Разбираемся с hashCode() и equals()

Что такое хеш-код?

Если очень просто, то хеш-код — это число. На самом деле просто, не так ли? Если более точно, то это битовая строка фиксированной длины, полученная из массива произвольной длины.

Пример №1
Выполним следующий код:

В результате выполнения программы в консоль выведется целое 10-ти значное число. Это число и есть наша битовая строка фиксированной длины. В java она представлена в виде числа примитивного типа  int, который равен 4-м байтам, и может помещать числа от -2 147 483 648 до 2 147 483 647. На данном этапе важно понимать, что хеш-код это число, у которого есть свой предел, который для java ограничен примитивным целочисленным типом int.

Вторая часть объяснения гласит:

полученная из массива произвольной длины.

Под массивом произвольной длины мы будем понимать объект. В 1 примере в качестве массива произвольной длины у нас выступает объект типа  Object.

В итоге, в терминах Java, хеш-код — это целочисленный результат работы метода, которому в качестве входного параметра передан объект.

Этот метод реализован таким образом, что для одного и того-же входного объекта, хеш-код всегда будет одинаковым. Следует понимать, что множество возможных хеш-кодов ограничено примитивным типом  int, а множество объектов ограничено только нашей фантазией. Отсюда следует утверждение: “Множество объектов мощнее множества хеш-кодов”. Из-за этого ограничения, вполне возможна ситуация, что хеш-коды разных объектов могут совпасть.

Здесь главное понять, что:

  • Если хеш-коды разные, то и входные объекты гарантированно разные.
  • Если хеш-коды равны, то входные объекты не всегда равны.

Ситуация, когда у разных объектов одинаковые хеш-коды называется — коллизией. Вероятность возникновения коллизии зависит от используемого алгоритма генерации хеш-кода.

Подведём итог:

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

  1. для одного и того-же объекта, хеш-код всегда будет одинаковым;hashCode() и equals()
  2. если объекты одинаковые, то и хеш-коды одинаковые (но не наоборот, см. правило 3).hashCode() и equals()
  3. если хеш-коды равны, то входные объекты не всегда равны (коллизия);hashCode() и equals()
  4. если хеш-коды разные, то и объекты гарантированно разные;hashCode() и equals()

Понятие эквивалентности. Метод equals()

Начнем с того, что в java, каждый вызов оператора  new порождает новый объект в памяти. Для иллюстрации создадим какой-нибудь класс, пускай он будет называться “BlackBox”.

Пример №2
Выполним следующий код:

Создадим класс для демонстрации  BlackBox.

Во втором примере, в памяти создастся два объекта.

hashCode() и equals()

Но, как вы уже обратили внимание, содержимое этих объектов одинаково, то есть эквивалентно. Для проверки эквивалентности в классе  Object существует метод  equals(), который сравнивает содержимое объектов и выводит значение типа  boolean true, если содержимое эквивалентно, и  false— если нет.

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

Иными словами:

Я написал “должно быть”, потому что если вы выполните предыдущий пример, то на самом деле результатом выполнения всех операций будет  false. Для пояснения причин, заглянем в исходные коды класса  Object.

Класс Object

Как известно, все java-классы наследуются от класса Object. В этом классе уже определены методы  hashCode() и  equals().
Определяя свой класс, вы автоматически наследуете все методы класса  Object. И в ситуации, когда в вашем классе не переопределены ( @overridinghashCode() и  equals(), то используется их реализация из  Object.

Рассмотрим исходный код метода  equals() в классе  Object.

При сравнение объектов, операция “ ==” вернет  true лишь в одном случае — когда ссылки указывают на один и тот-же объект. В данном случае не учитывается содержимое полей.

Выполнив приведённый ниже код,  equals вернет  true.

hashCode() и equals()

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

Заглянем в исходный код метода  hashCode() в классе  Object:

Вот собственно и вся реализация. Ключевое слово  native означает, что реализация данного метода выполнена на другом языке, например на C, C++ или ассемблере. Конкретный  native int hashCode()реализован на C++, вот исходники — http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/tip/src/share/vm/runtime/synchronizer.cpp функция  get_next_hash.

При вычислении хэш-кода для объектов класса  Object по умолчанию используется Park-Miller RNG алгоритм. В основу работы данного алгоритма положен генератор случайных чисел. Это означает, что при каждом запуске программы у объекта будет разный хэш-код.

Получается, что используя реализацию метода  hashCode() от класса  Object, мы при каждом создании объекта класса  new BlackBox(), будем получать разные хеш-коды. Мало того, перезапуская программу, мы будем получать абсолютно разные значения, поскольку это просто случайное число.

Но, как мы помним, должно выполняться правило: “если у двух объектов одного и того же класса содержимое одинаковое, то и хеш-коды должны быть одинаковые ”. Поэтому, при создании пользовательского класса, принято переопределять методы  hashCode() и  equals() таким образом, что бы учитывались поля объекта.
Это можно сделать вручную либо воспользовавшись средствами генерации исходного кода в IDE. Например, в Eclipse это Source → Generate hashCode() and equals()…

В итоге, класс BlackBox приобретает вид:

Теперь методы  hashCode() и  equals() работают корректно и учитывают содержимое полей объекта:

 

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

6 − шесть =