Как стать автором
Обновить

Методы .equals и .hashcode в Java. Отличия реализации по умолчанию от реализации на практике

Время на прочтение 3 мин
Количество просмотров 29K

Сразу же скажу, что статья во многом опирается базовые понятия алгебры, которые к великому счастью легко и быстро осознаются при помощи всего-лишь метода внимательного разглядывания. Поехали.

В Java так устроено, что любой класс, который вы определяете, наследуется от класса Object. Таким образом класс Object является суперклассом любого класса в любой программе.

Это означает, что абсолютно любой класс содержит методы, которые определены в классе Object. Методы .equals() и .hashcode() - одни из них.

Прежде всего я должен описать главные правила для любых реализаций этих двух методов, которые нужно обязательно соблюдать, запомнить как аксиому:


1). Если x.equals(y) == true, то обязательно hashcode(x) == hashcode(y)

2) Если hashcode(x) == hashcode(y), то не обязательно x.equals(y) == true


Метод .equals()

Отношение эквивалентности (алгебра)

Прежде чем поговорить о методе .equals, я бы хотел рассказать, что такое отношение эквивалентности с точки зрения алгебры (пока-что забудьте про программирование).

Отношение эквивалентности - это бинарное (бинарное - значит между двумя) отношение, которое является:

  • симметричным (для любых x, y выполняется: если x = y, то y = x)

  • рефлексивным (для любого x выполняется: x = x)

  • транзитивным (для любых x, y, z выполняется: если x = y и y = z, то x = z)

Таким образом, если на множестве определено отношение эквивалентности, множество можно разделить на подмножества - классы эквивалентности.

Каждый класс эквивалентности содержит внутри себя только те элементы, которые эквиваленты (более формально - находятся в отношении эквивалентности) между собой.

Реализация .equals() по умолчанию

Метод .equals() в классе Object реализован примерно следующим образом:

public boolean equals(Object x) {
  return(this == y)
}

Фактически он делает следующее: Он принимает в качестве аргумента ссылочную переменную и проверяет, ссылается ли они на тот же объект (ту же область памяти, если быть точнее), что и объект, к которому мы применили метод .equals().

Таким образом, стандартная реализация .equals() выстраивает отношение эквивалентности, которое можно описать так: две ссылки эквивалентны, если они ссылаются на одну и ту же область памяти.

Такая реализация не противоречит математической идеологии, описанной выше. Однако на практике метод .equals() часто переопределяют в подклассах.

Как и зачем переопределяют метод .equals()?

Очевидно, гораздо более применимой будет возможность сравнивать объекты по какому-нибудь другому критерию. Часто метод .equals() переопределяют так, чтобы он сравнивал объекты по значениям их полей.

К примеру, если классы двух объектов, на которые указывают ссылки, совпадают и все значения их полей совпадают, то эти два объекта эквивалентны между собой. Легко проследить, что такое определение не противоречит математической идеологии.

Конкретную кодовую реализацию я приводить не буду, потому что она не так важна, как сама идея

Это и другие возможные переопределения метода .equals() мало того, что расширяют круг наших возможностей, так ещё и не лишают старых, ведь мы по прежнему имеем возможность проверять, ссылаются ли две ссылки на одну область памяти, используя операнд ==, вместо прежнего .equals()

return(ob1 == ob2);

Метод .hashcode()

Сюръекция (алгебра)

Сюръекция - сопоставление элементам множества X элементов второго множества Y, при котором для любого элемента из Y есть хотя-бы один сопоставленный элемент из X.

Если немного более подробно разобрать это определение, то мы увидим следующее:

  • Даже несколько элементов из X могут быть сопоставлены одному и тому же элементу из Y (это называется коллизией).

  • Возможно есть такое элемент из X, и даже возможно не один, что он не сопоставлен никакому элементу из Y. (см. рисунок, всё интуитивно)

C -> Z, D -> Z - коллизия
E элемент, которому ничего не сопоставлено
C -> Z, D -> Z - коллизия E элемент, которому ничего не сопоставлено

Что происходит в java?

Метод .hashcode() как-раз осуществляет сюръекцию. Множеством X выступает множество всевозможных объектов которые мы можем создать, множеством Y выступает область значений типа данных int. Метод .hashcode() вычисляет каким-то скрытым от нас способом целое число, опираясь на объект, к которому применяется.

Единственное отличие метода .hashcode() от сюръекции в том, что любой объект может быть обработан методом .hashcode()

Здесь нет элементов по типу E из пред. рисунка
Здесь нет элементов по типу E из пред. рисунка

Реализация .hashcode() по умолчанию?

Насколько я понял, точно так никто в этом и не разобрался. Есть много версий:

  • Значение .hashcode() - это область памяти, где лежит объект

  • Значение .hashcode() - это число, создаваемое генератором случайных чисел в какой-то момент

  • Сама функция написана не на Java а вообще на C.

И многие другие. В общем каким-то образом она всё же устроена, но самое главное в том, что стандартная реализация .hashcode() со стандартной реализацией .equals() подчиняются правилу, приведённому в самом начале статьи

Как и зачем переопределяют метод .hashcode()?

Основной причиной для изменения метода .hashcode() является то, что желают изменить .equals(), однако смена стандартной реализации .equals() приводит к нарушению правила из начала статьи

Второстепенной причиной для изменения метода .hashcode() является то, что желают изменить вероятность коллизии (эта причина встречается реже)

Конец :)

Теги:
Хабы:
-5
Комментарии 18
Комментарии Комментарии 18

Публикации

Истории

Работа

Java разработчик
359 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн