September 17, 2023

The hashCode and equals methods

Какие бывают ассоциативные массивы (Map)? Какие из них быстрее? Что нужно сделать, чтобы они заработали быстрее? Для чего нужны ф-ии hashCode и equals?

Рассмотрим основные моменты, для понимания потребности в методах. Разделим ассоциативные массивы на три различных типа мапов:

HashMap(выводит элементы без порядка)(Для хранения элементов используются бакеты(Backet/Slot))

LinkedHashMap(вывел элементы в том порядке, в каком и добавляли)(Для хранения элементов используются бакеты(Backet/Slot))

TreeMap(выводит элементы в порядке отсортированном по ключу)(является самым медленным, по той причине, что когда мы добавляем элемент, он сравнивает его ключ с ключами присутствующих элементов, и основываясь на результате сравнения, в процессе добавления, сортирует порядок)

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

В TreeMap ключи должны соответствовать интерфейсу Comparable. Для этого достаточно имплементировать интерфейс Comparable и реализовать ф-ю compareTo() которая возвращает значение Integer. (TreeMap не использует ф-ю equals).

В HashMap, LinkedHashMap, нам нужно переопределить ф-ии hashCode и equals, таким образом, чтобы сравнить все внутренние элементы нашего класса.

Контракт метода equals

  1. Рефлексивность (reflexive): Объект должен быть равен самому себе, т.е. для каждого нулевого x, x.equals(x) должно возвращать true.
  2. Симметричность (symmetric): Объекты должны быть равны друг другу, т.е. для ненулевых x и y, x.equals(y) должно возвращать true только если y.equals(x) возвращает true.
  3. Транзитивность (transitive): Для ненулевых x, y, z (три объекта), если x.equals(y) возвращает true, и y.equals(z) возвращает true, то x.equals(z) должно возвращать true.
  4. Повторяемость (consistent): Для ненулевых x и y последовательные вызовы x.equals(y) должны постоянно возвращать true или постоянно возвращать false при условии, что x и y не меняются.

Контракт метода HashCode

  1. У двух одинаковых объектов должен быть одинаковый хеш-код.
  2. У двух разных объектов могут быть одинаковые хеш-коды. (Как написать функцию, чтобы избежать возникновение коллизии?)
  3. Нельзя возвращать константу или рандомное число.

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

Соответственно, при проектирования какого либо класса, если мы понимаем, что будем сравнивать объекты этого класса, то необходимо реализовать метод hashCode.

Переопределение методов (@Override)
При переопределении методов, в большинстве случаев, мы выбираем все поля нашего класса. Методы hashCode и equals переопределяются всегда вместе, так как связаны одним контрактом.

@Override
public boolean equals(Object o) { 
if (this == o) return true; 
if (o == null || getClass() != o.getClass()) return false; 
Human human = (Human) o; 
return id == human.id && Objects.equals(firstName, human.firstName) && Objects.equals(lastName, human.lastName);}

if (this == o) return true; - говорит нам о том, что наши ссылки равны друг другу.

if (o == null || getClass() != o.getClass()) return false; - если объект равен нулю и объект не принадлежит одному и тому же классу(т.е. классы не равны), то возвращаем false.

@Override
public int hashCode() { 
return Objects.hash(id, firstName, lastName);}

return Objects.hash(id, firstName, lastName); - возвращаем хэш код нашего объекта.

В нашем примере, мы можем сами устанавливать критерии сравнения наших объектов. Например, сравнивать только по id. Или сравнивать по всем параметрам типа, как указано выше.


Что такое бакеты(bucket(-ведро))? Набор элементов хеш-таблицы с совпадающими/близкими значением хеш-функции.
У каждого объекта в JVM, есть функция HashCode, когда мы что то добавляем в HashMap или LinkedHashMap, коллекция высчитывает хеш-код элемента и основываясь на результате, кладет его в структуру данных. При добавлении нового элемента, операция повторяется и вычисляется новый хеш, после чего определяется место для элемента.
Когда мы будем искать по ключу определенное значение из нашей коллекции, сначала для ключа просчитается хеш-код, по хещ-коду будет найден бакет, в котором лежит наш объект и дальше будет доставаться значение для этого объекта, предварительно сравнив с конкретным ключом, который лежит в этом бакете.

Что возвращает ф-я compareTo()? Функция возвращает целочисленное значение Integer. "-1" если левый объект, меньше чем правый. "1" если левый больше чем правый. "0" если они равны


Полезные ссылки:

(https://www.youtube.com/watch?v=6qVRci8gG-M)

(https://ru.stackoverflow.com/questions/988722/%D0%A7%D1%82%D0%BE-%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-buckets-%D0%B8%D0%BB%D0%B8-%D0%B1%D0%B0%D0%BA%D0%B5%D1%82)

(https://medium.com/@vaibhav0109/https-medium-com-vaibhav0109-java-hashcode-collision-how-uniform-is-its-distribution-ee4e5e8dc894)

(https://itsobes.ru/JavaSobes/equals-hashcode/)