JavaRush /Java блог /Random /Кофе-брейк #168. Зачем переопределять методы equals и has...

Кофе-брейк #168. Зачем переопределять методы equals и hashcode в Java?

Статья из группы Random

Зачем переопределять методы equals и hashcode в Java?

Источник: Medium Содержание этой статьи посвящено двум тесно связанным между собой методам: equals() и hashcode(). Вы узнаете, как они взаимодействуют друг с другом и как их правильно переопределять. Кофе-брейк #168. Зачем переопределять методы equals и hashcode в Java? - 1

Почему мы переопределяем метод equals()?

В Java мы не можем перегружать поведение таких операторов, как ==, +=, -+. Они работают согласно заданному процессу. Для примера рассмотрим работу оператора ==.

Как работает оператор ==?

Он проверяет, указывают ли две сравниваемые ссылки на один и тот же экземпляр в памяти. Оператор == будет иметь значение true только в том случае, если эти две ссылки представляют один и тот же экземпляр в памяти. Давайте взглянем на пример кода:

public class Person {
      private Integer age;
      private String name;
    
      ..getters, setters, constructors
      }
Допустим, в вашей программе вы создали два объекта Person в разных местах и ​​хотите их сравнить.

Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println( person1 == person2 ); --> will print false!
С точки зрения бизнеса эти два объекта выглядят одинаково, верно? Но для JVM они не совпадают. Поскольку они оба созданы с помощью ключевого слова new, эти экземпляры расположены в разных сегментах памяти. Поэтому оператор == вернет false. Но если мы не можем переопределить оператор ==, то как нам сказать JVM, что мы хотим, чтобы эти два объекта обрабатывались одинаково? Здесь в игру вступает метод .equals(). Вы можете переопределить equals(), чтобы проверить, имеют ли некоторые объекты одинаковые значения для определенных полей, чтобы считать их равными. Вы можете выбрать, какие поля нужно сравнить. Если мы говорим, что два объекта Person будут одинаковыми только тогда, когда они имеют одинаковый возраст и одно и то же имя, то в этом случае IDE сгенерирует для автоматического создания equals() что-то такое:

@Override
public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                name.equals(person.name);
    }
Вернемся к нашему предыдущему примеру.

Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
System.out.println ( person1.equals(person2) ); --> will print true!
Да, мы не можем перегрузить оператор == для сравнения объектов так, как мы хотим, но Java дает нам другой способ — метод equals(), который мы можем переопределить по своему усмотрению. Имейте в виду, что если мы не предоставим нашу пользовательскую версию .equals() (также известную как переопределение) в нашем классе, то предопределенный .equals() из класса Object и оператор == будут вести себя одинаково. Метод по умолчанию equals(), унаследованный от Object, будет проверять, совпадают ли оба сравниваемых экземпляра в памяти!

Почему мы переопределяем метод hashCode()?

Некоторые структуры данных в Java, такие как HashSet и HashMap, хранят свои элементы на основе хеш-функции, которая применяется к этим элементам. Хеш-функцией является hashCode(). Если у нас есть выбор в переопределении метода .equals(), то у нас также должен быть выбор в переопределении метода hashCode(). Для этого есть причина. Ведь реализация по умолчанию hashCode(), унаследованная от Object, считает все объекты в памяти уникальными! Но вернемся к этим структурам хеш-данных. Для этих структур данных существует правило. HashSet не может содержать повторяющиеся значения, а HashMap не может содержать повторяющиеся ключи. HashSet реализован с помощью HashMap таким образом, что каждое значение HashSet хранится как ключ в HashMap. Как работает HashMap? HashMap — это собственный массив с несколькими сегментами. Каждый сегмент имеет связанный список (linkedList). В этом связанном списке хранятся наши ключи. HashMap находит правильный linkedList для каждого ключа, применяя метод hashCode(), а затем выполняет итерацию по всем элементам этого linkedList и применяет метод equals() к каждому из этих элементов, чтобы проверить, содержится ли там этот элемент. Дубликаты ключей не допускаются. Кофе-брейк #168. Зачем переопределять методы equals и hashcode в Java? - 2Когда мы помещаем что-то внутрь HashMap, то ключ сохраняется в одном из этих связанных списков. В каком связанном списке будет храниться этот ключ, показывает результат метода hashCode() для этого ключа. То есть, если key1.hashCode() в результате получается 4, то этот key1 будет храниться в 4-м сегменте массива в существующем там LinkedList. По умолчанию метод hashCode() возвращает разные результаты для каждого экземпляра. Если у нас есть значение по умолчанию equals(), которое ведет себя как ==, рассматривая все экземпляры в памяти как разные объекты, то проблем не будет. Как вы помните, в нашем предыдущем примере было сказано, что мы хотим, чтобы экземпляры Person считались равными, если их возраст и имена совпадают.

Person person1 = new Person("Mike", 34);
    Person person2 = new Person("Mike", 34);
    System.out.println ( person1.equals(person2) );  --> will print true!
Теперь давайте создадим карту (map) для хранения этих экземпляров в виде ключей с определенной строкой в ​​качестве парного значения.

Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
В классе Person мы не переопределили метод hashCode, но у нас есть переопределенный метод equals. Поскольку значение по умолчанию hashCode дает разные результаты для разных Java-экземпляров person1.hashCode() и person2.hashCode(), есть большие шансы получить разные результаты. Наша карта может заканчиваться разными person в разных связанных списках. Кофе-брейк #168. Зачем переопределять методы equals и hashcode в Java? - 3Это противоречит логике HashMap. Ведь HashMap не может иметь несколько одинаковых ключей! Дело в том, что по умолчанию hashCode(), унаследованного от класса Object, недостаточно. Даже после того, как мы переопределили метод equals() класса Person. Вот почему мы должны переопределить метод hashCode() после того, как мы переопределили метод equals. Теперь давайте это исправим. Нам нужно переопределить наш метод hashCode(), чтобы он учитывал те же поля, что и equals(), а именно age и name.

public class Person {
      private Integer age;
      private String name;
    
      ..getters, setters, constructors
@Override
public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                name.equals(person.name);
    }
@Override
public int hashCode() {
        int prime = 31;
        return prime*Objects.hash(name, age);
    }
В методе hashCode() мы использовали простое значение (вы можете использовать любые другие значения). Тем не менее, предлагается использовать простые числа, чтобы создавать меньше проблем. Давайте попробуем еще раз сохранить эти ключи в нашем HashMap:

Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
person1.hashCode() и person2.hashCode() будут одинаковы. Допустим, равны 0. HashMap перейдет в сегмент 0 и в нем LinkedList сохранит person1 как ключ со значением “1”. Во втором случае, когда HashMap снова перейдет к корзине 0, чтобы сохранить ключ person2 со значением “2”, он увидит, что там уже существует другой равный ему ключ. Таким образом он перезапишет предыдущий ключ. И в нашем HashMap будет существовать только ключ person2. Кофе-брейк #168. Зачем переопределять методы equals и hashcode в Java? - 4Так мы узнали, как работает правило HashMap, которое гласит, что нельзя использовать несколько одинаковых ключей! Однако имейте в виду, что неравные экземпляры могут иметь одинаковый хэшкод, а одинаковые экземпляры должны возвращать одинаковый хэшкод.
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Gans Electro Уровень 50
23 октября 2022
Очень понятно и полезно