hashcode() и equals() в java, зачем переопределять?

Home » JavaCore » hashcode() и equals() в java, зачем переопределять?
JavaCore, Uncategorized Комментарии (2)

Нет ничего совершенного, однако к идеалу можно приблизиться

hashcode() и equals() в java, зачем переопределять?

Для начала. Объекты в java могут быть между собой равны, а могут быть эквивалентны

1) равными(через знак ==) могут быть только ссылки, не объекты.
2) эквивалентными называются объекты с одинаковыми значениями полей
но equals != эквивалентность
объясним почему:

hashcode()

hashcode() – метод Object, возвращающий int значение.

В классе Object метод hashCode() определен как public native int hashCode();
исходный код hashCode() написан на другом языке.
Алгоритм генерации хешкода для объекта работает с учаcтием генератора случайных чисел, то есть при каждом выполнении программы хеш-коды объектов будут разными

Однако следует пристально обратить внимание на одно разительное несовершенство функции hashCode():
Если хешкоды объектов не равны, то объекты гарантированно разные. Но, неверно обратное,- если хешкоды объектов равны, то и сами объекты равны.
Ситуация, когда у разных объектов появляются одинаковые хешкоды называется коллизией.
A – хешкод x равен хешкоду y
B – x = y
1) -A -> -B если хешкоды объектов неравны, то объекты неравны
2) -(A -> B) неверно, что если хешкоды равны, то объекты равны
таким образом
(-A -> -B) & (-(A -> B))
(-A \/ B) & (-(A \/ -B))
(-A \/ B) & (-A /\ B)
получаем две истины
-A \/ B
1) хешкоды неравны, и объекты неравны
2) хешкоды равны, объекты неравны
3) хешкоды равны, объекты равны

-A /\ B и есть ситуация коллизии, хешкоды неравны, но объекты равны

equals()

В идеале етод equals() класса Object должен проверять объекты на эквивалентность, исходная же реализация метода equals в классе Object

equals() тупо сравнивает ссылки (ссылки на адреса памяти зарезервированные под объекты), и если они ссылаются на один и тот же объект, возвращает true
equals() не сравнивает значения полей объектов!

Резюмируем:
Итак мы выяснили, что стандартная реализация методов equals() и hashCode() предоставляет из себя весьма скудный и абстрактный инструментарий. Hashcode() выдает нам не всегда уникальный int, а equals по умолчанию, не сравнивает значения полей объекта, а реализован в виде простого ссылочного сравнения.

В чем проблема equals() и hashСode() и зачем их нужно переопределять?

Джошуа Блох в книге “Эффективное программирование” писал так, –
“Переопределяя метод equals() всегда переопределяйте hashCode()”

эээ.. так а в чем проблема? В чем проблема equals() и hashcode() и зачем их нужно переопределять? по умолчанию. инструментарий java не обеспечивает алгоритма сравнения объектов на предмет равенства за исключением сравнения ссылок на адреса разделов памяти, зарезервированных под объекты. Так же, по умолчанию java не гарантирует безупречную(полностью удовлетвлряющую требованиям) работу по идентификации объектов с помощью хешкодов. Иными словами, по умолчанию сравнение объектов в java осуществляется сравнением ссылок на адреса памяти. Но проблема в том, что многие важные инструменты java, такие, как инфраструктура коллекций, переопределяют метод для сравнения equals, используя при этом метод hashcode*. Таким образом, если мы не уделяем внимания объектам(не переопределяем вышеназванные методы), которые могут содержаться в этих типах коллекций, мы подспудно, сами того не осознавая, закладываем в код потенциальную возможность ошибок, связанных с несовершенством этих методов.
Например, мы создаем большую HashMap. Так как хешкод по умолчанию может выдавать одинаковые int-ы, то каждый раз когда мы захотим положить объект в hashmap() ( с помощью put())а его хеш вдруг случайно совпадет с хешем уже существующего в мэпе объекта**(так как ключами в HashMap е являются хеши), то мы не добавим новый объект а просто перетрем значение старого новым значением! Вот так, незатейливо произойдет ошибка.

Переопределяем equals()

Итак, по умолчанию, “равенства” объектов как таковых в java нет. Равными могут быть ссылки. Равенство ссылок означает, что они ссылаются на один и тот же объект.
Однако, правило равенства между объектами одного типа, для того, чтобы их можно было сравнивать, можно написать самому. Что и настоятельно следует делать, переопределяя метод equals()! Вспомним сейчас, что если мы переопределяем equals() то нам также следует переопределить и hashCode, ибо как и было рассказано в предыдущем абзаце, многие важные классы пользуются equals() и hashCode().

I. Самый простой способ реализовать equals() и hashCode() в своем классе, воспользоваться авто-генерацией через IDE. Например, в JIdea это можно сделать с помощью alt + insert.

Также есть ряд требований и рекомендаций к реализации equals() и hashCode()

Реализация equals() должна обладать тремя важными свойствами бинарных отношений
1) рефлексивность: a.equals(a)=true;
2) симметричность: if((a.equals(b))){b.equals(a);}
3) транзитивность: if((a.equals(b))&&(b.equals(c))){a.equals(c)}
4) equals() должна возвращать неизменное значение, пока какое-либо из свойств объектов не изменяется.
Пока свойства объектов остаются неизменными, они должны оставаться равны.
4) Повторные вызовы метода equals() должны возвращать одно и тоже значение до тех пор, пока какие-либо значения свойств объекта не будут изменены. То есть, если два объекта равны в Java, то они будут равны пока их свойства остаются неизменными.
5) Необходимо проверять объект на null. В случае, если объект = null, equals() должен возвраoать false, а не выкидывать исключение.
6) не помешает проверка соответствия типа(для этого следует использовать getClass())
7) необходимо привести в соответствие equals() и compareTo() для корректной работы hash-коллекций.***

Резюме

a.hashCode() != b.hashCode() -> !(a.equals(b))
a.hashCode() = b.hashCode() -> ((a.equals(b)) \/ !(a.equals(b)))
Что вносит ущербность в реализацию некоторых коллекций****, основные алгоритмы которых(алгоритмы проверки объектов на предмет равенства) основаны на функциях equals() и hashCode().
Посему в случае, если объекты, планируется содержать в коллекциях, методы которых основаны на данных алгоритмах, рекомендуется переопределять эти методы и приводить их в соответствие бизнес-логике программы

Примечания


**механизм put() класса HashMap несколько сложнее, HashMap содержит массив объектов Enrty, связанный список
***Распространенные ошибки при переопределении equals в Java
****В первую очередь, это коллекции HashSet и HashMap

Ссылки

перегрузка методов equals() и hashCode() в java
переопределяем hashCode() и equals()
как работает HashMap

2 thoughts on - hashcode() и equals() в java, зачем переопределять?

  • Не следует реализовывать equality members для абсолютно всех пользовательских классов, поскольку наличие реализации equals и hashcode намекает программисту, читающему код, на то, что объекты класса используются в качестве ключа в хешмапах.

LEAVE A COMMENT