article-spots
article-carousel-spots
programs
Технологии

Изучаем Java. Методы equals() и hashCode()

21 июля 2020

В этой статье мы рассмотрим два важных метода класса Object в Java - equals() и hashCode(), разберемся, зачем они нужны и расскажем об их использовании на конкретных примерах.

Зачем нужны equals() и hashCode() и в чём их особенность?

При программировании нередко возникают ситуации, когда нужно сравнивать объекты. Первое, что приходит на ум, когда хочется что-то сравнить, это проверить условие написав символ “==”. Кажется, что это должно решить все наши проблемы, однако это не так. Символ “==” имеет смысл использовать только с примитивами. Что касается объектов, “==” в Java сравнивает только ссылки, то есть проверяет, равен ли объект самому себе, а это не всегда может быть полезно. Для сравнения объектов в классе Object присутствует метод equals(), именно он и должен выполнять сравнение объектов. По умолчанию этот метод выполняет внутри себя операцию “==”, однако, идея этого метода состоит в том, что он должен быть переопределён для создаваемых классов. 

Каким же должен быть переопределённый метод equals()?

Метод выглядит следующим образом: public boolean equals(Object o);

На вход он принимает Object и должен вернуть true или false. Теперь пройдемся по шагам.

Как же нужно произвести сравнение двух объектов в Java, чтобы оно выполнялось качественно и эффективно?

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

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

3) Теперь, когда мы уверены что оба объекта относятся к одинаковому классу и не равны null, можно выполнить приведение объекта к нужному типу и приступить к сравнению полей.  

Пример

А вот и пример переопределённого метода, который у нас получился:

Выглядит неплохо, но стоит отметить, что вызываемые методы equals() для String и Integer уже переопределены внутри этих классов. Если полем вашего класса является объект другого вашего класса, в нём так же необходимо переопределять метод equals(). Кроме того, представленный метод полностью соответствует определённым для него требованиям, прописанным в документации Oracle, а именно: 

1) Рефлексивность. Любой объект не равный null должен быть equals() самому себе.

2) Симметричность. Если a.equals(b) == true, то и b.equals(a) должно возвращать true.

3) Транзитивность. Если два объекта равны какому-то третьему объекту, значит, они должны быть равны друг и другу.

4) Постоянность. Результаты работы equals() должны меняться только при изменении входящих в него полей. Если данные двух объектов не менялись, результаты проверки на equals() должны быть всегда одинаковыми. 

5) Неравенство с null. Для любого объекта не равного null проверка a.equals(null) должна возвращать false

Но что делать если нам нужно лишь убедиться в том, что объекты не равны друг другу и сделать это очень быстро?

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

В Java хэш-код объекта можно получить с помощью метода hashCode(). Для любого объекта этот метод должен возвращать 32-битное значение типа int. Возвращаемый хэш-код должен удовлетворять требованиям, прописанным в документации Oracle, а именно:

1) Если два объекта равны (т. е. метод equals() возвращает true), у них должен быть одинаковый хэш-код.

2) Если метод hashCode() вызывается несколько раз на одном и том же объекте, каждый раз он должен возвращать одно и то же число.

3) Одинаковый хэш-код может быть у двух разных объектов. 

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

Пример

Рассмотрим пример того, как может быть переопределён метод hashCode() на примере нашего класса:

Умножение на простое нечётное число 31 используется для уменьшения числа коллизий, т. е. чтобы разнообразие рассчитываемых хэш-кодов было как можно больше. Есть и более приятный глазу способ переопределения хэш-кода. В классе Objects есть метод hash(), на вход которого нужно подать все поля класса. Этот метод делает то же самое что показано на предыдущей картинке:

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