Связь между методом hashCode и equals в Java

Я читал во многих местах, говоря, что, переопределяя метод equals в Java, должен переопределить метод hashCode, иначе он "нарушит контракт".

Но до сих пор я не сталкивался с какой-либо проблемой, если я переопределяю только метод equals, но не метод hashCode.

Что такое контракт? И почему я не сталкиваюсь с какой-либо проблемой, когда нарушаю контракт? В этом случае я столкнулся бы с проблемой, если бы не переопределил метод hashCode?

Ответ 1

Проблема, с которой вы столкнетесь, - это коллекции, в которых однозначность элементов вычисляется в соответствии с .equals() и .hashCode(), например, в HashMap.

Как следует из его названия, он полагается на хэш-таблицы, а хэш-ведра - это функция объекта .hashCode().

Если у вас есть два объекта, которые .equals(), но имеют разные хэш-коды, вы теряете!

Здесь важна часть контракта: объекты, которые .equals() ДОЛЖНЫ иметь один и тот же .hashCode().

Все это описано в javadoc для Object. И Джошуа Блох говорит, что вы должны сделать это в Эффективная Java. Достаточно сказано.

Ответ 2

Согласно документу, реализация hashCode по умолчанию возвращает целое число, которое отличается для каждого объекта

Насколько это разумно практично, метод hashCode, определенный классом Object, возвращать различные целые числа для разных объектов. (Обычно это реализуется преобразование внутреннего адреса объекта в целое число, но эта реализация
метод не требуется языком программирования JavaTM.)

Однако некоторое время вы хотите, чтобы хэш-код был одинаковым для разных объектов, имеющих одинаковое значение. Например

Student s1 = new Student("John", 18);
Student s2 = new Student("John", 18);
s1.hashCode() != s2.hashCode(); // With the default implementation of hashCode

Эта проблема возникает, если вы используете структуру хеш-данных в рамках коллекции, такую ​​как HashTable, HashSet. Специально для коллекции, такой как HashSet, вы получите дублирующий элемент и нарушите контракт Set.

Ответ 3

Да, это должно быть отменено. Если вы считаете, что вам нужно переопределить equals(), вам необходимо переопределить hashCode() и наоборот. Общий контракт hashCode():

  • Всякий раз, когда он вызывается на одном и том же объекте более одного раза во время выполнения приложения Java, метод hashCode должен последовательно возвращать одно и то же целое число, если информация, используемая при равных сравнениях с объектом, не изменяется. Это целое число не должно оставаться согласованным с одним исполнением приложения на другое выполнение того же приложения.

  • Если два объекта равны в соответствии с методом equals (Object), то вызов метода hashCode для каждого из двух объектов должен давать одинаковый результат целого.

  • Не требуется, чтобы, если два объекта неравны в соответствии с методом equals (java.lang.Object), то вызов метода hashCode для каждого из двух объектов должен производить различные целочисленные результаты. Тем не менее, программист должен знать, что создание отдельных целочисленных результатов для неравных объектов может улучшить производительность хэш-таблиц.

Ответ 4

Контракт: если два объекта равны, то они должны иметь один и тот же хэш-код, и если два объекта не равны, то они могут иметь или не иметь один и тот же хэш-код.

Попробуйте использовать свой объект как ключ в HashMap (отредактированный после комментария от joachim-sauer), и вы начнете сталкиваться с проблемами. Контракт - это руководство, а не что-то навязанное вам.

Ответ 5

См. JavaDoc java.lang.Object

В hashCode() говорится:

Если два объекта равны в соответствии с методом equals(Object)то вызов метода hashCode для каждого из двух объектов должен производят одинаковый целочисленный результат.

(Подчеркните мной).

Если вы переопределяете только equals(), а не hashCode(), ваш класс нарушает этот контракт.

Это также сказано в JavaDoc метода equals():

Обратите внимание, что обычно необходимо переопределить метод hashCodeкогда этот метод переопределяется, чтобы поддерживать общий контракт для метода hashCode, который утверждает, что равные объекты должны имеют одинаковые хэш-коды.

Ответ 6

Посмотрите на Hashtables, Hashmaps, HashSets и т.д. Все они хранят хешированный ключ в качестве своих ключей. При вызове get (ключ объекта) генерируется хэш параметра и просматривается в данных хэшах.

Если не переписывать hashCode() и экземпляр ключа был изменен (например, простая строка, которая вообще не имеет значения), hashCode() может привести к двум различным хэш-кодам для одного и того же объекта, в результате чего не найдя данный ключ в map.get().

Ответ 7

Контракт заключается в том, что если obj1.equals(obj2) then obj1.hasCode() == obj2.hashCode(), это в основном по соображениям производительности, так как карты в основном используют hashCode для сравнения ключей записей.