Каждый класс в Java всегда наследуется от класса Object, который является первым классом в иерархии наследования. Именно в этом классе определены два метода, которые в силу наследования Javaможет вызывать каждый объект:
...
public native int hashCode();
...
public boolean equals(Object obj) {
return (this == obj);
}
...
Что касается первого метода , его цель hashCode— вернуть целое число, которое «идентифицирует» объект при его сохранении в структуре данных, известной как HashMap (или другие, такие как Hashtable , HashSet ), целью которой является сохранение набора значений. (в форме, аналогичной a ArrayListили массиву). Эти структуры, за исключением некоторых различий, сохраняют данные следующим образом:
Когда вы хотите сохранить объект в этой структуре, вызывается метод hashCode(), как я уже упоминал, он возвращает целое число, которое структура будет использовать, чтобы решить, в какой « корзине » она будет сохранять эти данные. Теперь, если я хочу получить сохраненный объект, я hashCodeснова вызываю метод, тем самым определяя, из какого ящика извлекать объект (например, когда я сохраняю данные ключа/значения, он вызывает метод hashCodeключа для получения соответствующего значения) . Цель сохранения данных таким образом и вызова метода — обеспечить постоянное время хранения и извлечения (что не всегда так, но близко к этому). Не произойдет ли это, почти всегда зависит от значения, возвращаемого методом hashCode() для каждого объекта.
Предположим, мы сохраняем 3 объекта в этой структуре, и метод hashCode3 возвращает 0, это означает, что 3 объекта будут сохранены в ящике 0. Если я хочу их восстановить, произойдет так, что теперь я должен просмотреть объекты, которые я сохранили в этом ящике, чтобы определить, какой из них я хочу? Поэтому метод hashCode() этих объектов бесполезен, так как достигается то, что при сохранении элементы равномерно распределяются по всей структуре (остается наименьшее количество пустых ящиков и нет ящиков, в которых гораздо больше элементов). сохраняются, чем другие).
Что касается реализации метода hashCodeкласса Object, то идентификатор nativeуказывает на то, что ответственность за определение того, как он будет выполнять эту задачу, лежит на компиляторе. Во всех реализациях, которые я рассмотрел, он по умолчанию возвращает числовое представление «адреса памяти», по которому находится объект. Это кажется хорошей стратегией по умолчанию, но у нее есть большая проблема: адресов памяти намного больше, чем «количество сегментов», поэтому структура решает проблему (опять же, это также зависит от реализации), назначая ему номер hashcode()%MAX_LENGTHсегмента, где MAX_LENGTH максимальный размер массива. Итак, чем большее количество элементов нужно хранить, тем больше вероятность того, что в структуре будут ящики, имеющие избыток элементов.
По этой причине, если вам нужно хранить элементы в структуре такого типа, Javaрекомендуется переопределить этот метод. Некоторые IDE, такие как Eclipse или Netbeans, позволяют генерировать метод hashCodeиз свойств класса, и этого достаточно для решения этой проблемы.
Что касается второго метода , equals()реализация класса по умолчанию Objectясна: он сравнивает ссылки между объектами. Поэтому не переопределять его при определении класса — очень плохая идея, потому что не будет никакой разницы между вызовом equals()и использованием оператора ==. В документации четко указаны функции, которые должен иметь этот метод при сравнении:
Рефлексивность: если я сравниваю объект с самим собой, он должен возвращать true.
Симметрия: если я сравниваю объект A с объектом B и он возвращает true, то сравнение объекта B с объектом A также должно возвращать true.
Транзитивность: если я сравниваю объект A с объектом B, и он возвращается, trueи я сравниваю объект B с другим объектом C, и он возвращает true, при сравнении объекта A с объектом C он должен возвращать true.
Согласованность: если объекты A и B не изменены, последовательные вызовы метода должны возвращать одно и то же значение.
Теперь, как вы заметили, метод equals()не вызывает метод hashCode(), так зачем переопределять метод, hashCode()когда метод переопределен equals()? самый простой ответ — потому что документация предлагает так: если два объекта равны, они должны иметь одно и то же значение, возвращаемое hashCode(). Несмотря на то, что это предложение, снова операция над этими структурами показывает, почему это необходимо: если два объекта имеют одинаковый хэш-код, оба объекта будут храниться в одном и том же сегменте, структура теперь использует метод equals()
внутри этого сегмента, чтобы определить, какой из них соответствует к запрошенному, и для этого это зависит от того, переопределили ли вы метод, иначе это не гарантирует правильный результат.
В итоге:
Если вы переопределяете метод, equals()рекомендуется также переопределить метод hashCode(), чтобы сохранить контракт между обоими методами: два идентичных объекта должны возвращать одно и то же значение хэш-функции. Метод equals()не вызывает метод hashCode()для определения равенства двух объектов.
Рекомендуется, если не обязательно, переопределить метод, equals()потому что реализация по умолчанию не очень полезна.
Если два объекта не равны, нет необходимости переопределять метод hashCode(), даже два разных объекта могут возвращать одинаковые хеш-значения.
Если вам нужно сохранить объекты в указанных выше структурах ( HashMapи т.п.), абсолютно необходимо переопределить метод hashCode(), иначе вы получите неожиданные или нежелательные результаты при выполнении операций сохранения, запроса или удаления данных.
Нет. Этот метод equalsоценивает, равен ли один объект другому. Если класс не переопределяет метод equals, то результат использования equalsбудет таким же, как при использовании оператора ==между объектами. Метод hashCodeне связан с применением метода оценки equals.
Этот метод hashCodeиспользуется для получения хеш-кода, который был бы как бы идентификатором объекта. Этот хэш используется в некоторых коллекциях, таких как HashSet, HashMap, LinkedHashSet, LinkedHashMap, ConcurrentHashMapсреди прочих. Что делает хэш, так это помогает контейнеру найти элемент в структуре коллекции и помогает искать, существует ли уже объект с указанным хешем, это обеспечивает время поиска O (1) (при условии, что количество коллизий невелико) .
Пример работы алгоритма , где используются HashSet#addметоды hashCodeи equals. (Это идея, фактическая реализация более сложна и использует больше механизмов, чтобы гарантировать, следует ли увеличивать или уменьшать размер внутренней структуры, но это не относится к делу)
//es pseudocódigo, no es código Java
//se utiliza una matriz para guardar los elementos
Object[][] conjunto = ...
metodo agregar (elemento)
int hash = nuevo.hashCode();
si (hash < conjunto.length)
//pueden haber varios elementos con el mismo hash
//por ello se utiliza una matriz
//el hash ayuda a
Object[] elementosConMismoHash = arreglo[hash];
var encontrado = falso;
para cada Object actual en elementosConMismoHash
si actual.equals(nuevo) entonces
encontrado = verdadero;
romper para;
fin si
fin para
si no fue encontrado entonces
agregar nuevo en elementosConMismoHash
fin si
fin si
fin metodo
Как видите, в hashCodeкачестве идентификатора используется , но может быть несколько элементов с одинаковым результатом hashCode. Это не гарантирует , что объекты будут одинаковыми при использовании equals.
Из-за эффективности метода equals()хэш-код двух объектов обычно сравнивается в начале, так что если они не равны, он возвращается сразу false, избегая остальных сравнений.
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MiClase other = (MiClase) obj;
if (this.hashCode() != other.hashCode())
return false;
// Otras comparaciones
}
Каждый класс в Java всегда наследуется от класса
Object
, который является первым классом в иерархии наследования. Именно в этом классе определены два метода, которые в силу наследованияJava
может вызывать каждый объект:Что касается первого метода , его цель
hashCode
— вернуть целое число, которое «идентифицирует» объект при его сохранении в структуре данных, известной как HashMap (или другие, такие как Hashtable , HashSet ), целью которой является сохранение набора значений. (в форме, аналогичной aArrayList
или массиву). Эти структуры, за исключением некоторых различий, сохраняют данные следующим образом:Когда вы хотите сохранить объект в этой структуре, вызывается метод
hashCode()
, как я уже упоминал, он возвращает целое число, которое структура будет использовать, чтобы решить, в какой « корзине » она будет сохранять эти данные. Теперь, если я хочу получить сохраненный объект, яhashCode
снова вызываю метод, тем самым определяя, из какого ящика извлекать объект (например, когда я сохраняю данные ключа/значения, он вызывает методhashCode
ключа для получения соответствующего значения) . Цель сохранения данных таким образом и вызова метода — обеспечить постоянное время хранения и извлечения (что не всегда так, но близко к этому). Не произойдет ли это, почти всегда зависит от значения, возвращаемого методом hashCode() для каждого объекта.Предположим, мы сохраняем 3 объекта в этой структуре, и метод
hashCode
3 возвращает 0, это означает, что 3 объекта будут сохранены в ящике 0. Если я хочу их восстановить, произойдет так, что теперь я должен просмотреть объекты, которые я сохранили в этом ящике, чтобы определить, какой из них я хочу? Поэтому метод hashCode() этих объектов бесполезен, так как достигается то, что при сохранении элементы равномерно распределяются по всей структуре (остается наименьшее количество пустых ящиков и нет ящиков, в которых гораздо больше элементов). сохраняются, чем другие).Что касается реализации метода
hashCode
классаObject
, то идентификаторnative
указывает на то, что ответственность за определение того, как он будет выполнять эту задачу, лежит на компиляторе. Во всех реализациях, которые я рассмотрел, он по умолчанию возвращает числовое представление «адреса памяти», по которому находится объект. Это кажется хорошей стратегией по умолчанию, но у нее есть большая проблема: адресов памяти намного больше, чем «количество сегментов», поэтому структура решает проблему (опять же, это также зависит от реализации), назначая ему номерhashcode()%MAX_LENGTH
сегмента, где MAX_LENGTH максимальный размер массива. Итак, чем большее количество элементов нужно хранить, тем больше вероятность того, что в структуре будут ящики, имеющие избыток элементов.По этой причине, если вам нужно хранить элементы в структуре такого типа,
Java
рекомендуется переопределить этот метод. Некоторые IDE, такие как Eclipse или Netbeans, позволяют генерировать методhashCode
из свойств класса, и этого достаточно для решения этой проблемы.Что касается второго метода ,
equals()
реализация класса по умолчаниюObject
ясна: он сравнивает ссылки между объектами. Поэтому не переопределять его при определении класса — очень плохая идея, потому что не будет никакой разницы между вызовомequals()
и использованием оператора==
. В документации четко указаны функции, которые должен иметь этот метод при сравнении:true
.true
, то сравнение объекта B с объектом A также должно возвращатьtrue
.true
и я сравниваю объект B с другим объектом C, и он возвращаетtrue
, при сравнении объекта A с объектом C он должен возвращатьtrue
.Теперь, как вы заметили, метод
equals()
не вызывает методhashCode()
, так зачем переопределять метод,hashCode()
когда метод переопределенequals()
? самый простой ответ — потому что документация предлагает так: если два объекта равны, они должны иметь одно и то же значение, возвращаемоеhashCode()
. Несмотря на то, что это предложение, снова операция над этими структурами показывает, почему это необходимо: если два объекта имеют одинаковый хэш-код, оба объекта будут храниться в одном и том же сегменте, структура теперь использует методequals()
внутри этого сегмента, чтобы определить, какой из них соответствует к запрошенному, и для этого это зависит от того, переопределили ли вы метод, иначе это не гарантирует правильный результат.В итоге:
equals()
рекомендуется также переопределить методhashCode()
, чтобы сохранить контракт между обоими методами: два идентичных объекта должны возвращать одно и то же значение хэш-функции. Методequals()
не вызывает методhashCode()
для определения равенства двух объектов.equals()
потому что реализация по умолчанию не очень полезна.hashCode()
, даже два разных объекта могут возвращать одинаковые хеш-значения.HashMap
и т.п.), абсолютно необходимо переопределить методhashCode()
, иначе вы получите неожиданные или нежелательные результаты при выполнении операций сохранения, запроса или удаления данных.Нет. Этот метод
equals
оценивает, равен ли один объект другому. Если класс не переопределяет методequals
, то результат использованияequals
будет таким же, как при использовании оператора==
между объектами. МетодhashCode
не связан с применением метода оценкиequals
.Этот метод
hashCode
используется для получения хеш-кода, который был бы как бы идентификатором объекта. Этот хэш используется в некоторых коллекциях, таких какHashSet
,HashMap
,LinkedHashSet
,LinkedHashMap
,ConcurrentHashMap
среди прочих. Что делает хэш, так это помогает контейнеру найти элемент в структуре коллекции и помогает искать, существует ли уже объект с указанным хешем, это обеспечивает время поиска O (1) (при условии, что количество коллизий невелико) .Пример работы алгоритма , где используются
HashSet#add
методыhashCode
иequals
. (Это идея, фактическая реализация более сложна и использует больше механизмов, чтобы гарантировать, следует ли увеличивать или уменьшать размер внутренней структуры, но это не относится к делу)Как видите, в
hashCode
качестве идентификатора используется , но может быть несколько элементов с одинаковым результатомhashCode
. Это не гарантирует , что объекты будут одинаковыми при использованииequals
.Из-за эффективности метода
equals()
хэш-код двух объектов обычно сравнивается в начале, так что если они не равны, он возвращается сразуfalse
, избегая остальных сравнений.