Статьи

Java равно () и hashCode ()

Вступление:

Класс Java Object обеспечивает базовую реализацию методов — hashCode () и equals (). Эти методы чрезвычайно полезны, особенно при работе со структурой Collection. Реализации хеш-таблицы полагаются на эти методы для хранения и извлечения данных.

В этом уроке мы узнаем о контракте между hashCode () и equals () , их реализациями по умолчанию. Мы также поговорим о том, когда и как переопределить эти методы.

Поведение по умолчанию:

Давайте сначала посмотрим на реализации этих методов по умолчанию:

1. равно ():

Метод equals (), представленный в классе Object, просто сравнивает ссылки на объекты:

1
2
3
public boolean equals(Object obj) {
    return (this == obj);
}

Таким образом, по умолчанию obj1.equals (obj2) совпадает с obj1 == obj2 .

Метод equals () сравнивает фактические значения для таких классов, как String и т. Д., Поскольку он переопределяется в этих соответствующих классах.

2. hashCode ():

Подпись метода hashCode () в JDK:

1
public native int hashCode();

Здесь ключевое слово native указывает, что метод реализован в собственном коде с использованием JNI (Java Native Interface).

Метод hashCode () возвращает тип int. Возвращаемое значение по умолчанию представляет адрес памяти объекта.

Принципы реализации:

Прежде чем мы переопределим методы equals () и hashCode () , давайте сначала посмотрим на рекомендации:

1. равно ():   Наша реализация метода equals () должна быть:

  • Рефлексивный: для любого ссылочного значения obj , obj.equals (obj) должно возвращать true
  • Симметричный: для ссылочных значений obj1 и obj2 , если obj1.equals (obj2) равен true, тогда obj2.equals (obj2) также должен возвращать true
  • Транзитивно: для ссылочных значений obj1, obj2 и obj3 , если obj1.equals (obj2) имеет значение true и obj2.equals (obj3) имеет значение true, тогда obj1.equals (obj3) также должен возвращать true
  • Согласованно: при условии, что мы не изменили реализацию, множественные вызовы метода equals () всегда должны возвращать одно и то же значение

2. hashCode (): при реализации hashCode () мы должны учитывать следующие моменты:

  • В одном выполнении несколько вызовов hashCode () должны возвращать одно и то же значение, при условии, что мы не меняем свойство в реализации equals ()
  • равные объекты должны возвращать одинаковое значение hashCode ()
  • два или более неравных объекта могут иметь одинаковое значение hashCode ()

Контракт equals () и hashCode () :

Хотя все вышеперечисленные принципы необходимо учитывать при переопределении этих методов, среди них есть одно популярное правило:

Для двух объектов obj1 и obj2

  • Если obj1.equals (obj2), то obj1.hashCode () = obj2.hashCode () должно быть истинным
  • Однако, если obj1.hashCode () == obj2.hashCode () , то obj1.equals (obj2) может возвращать либо true, либо false, т. Е. Obj1 и obj2 могут или не могут быть равны

Обычно это называется контрактом equals () и hashCode () .

Почему переопределить equals () и hashCode ()?

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

Допустим, у нас есть класс Employee :

1
2
3
4
5
6
7
8
public class Employee {
  
    private int id;
    private String name;
  
    //constructors, getters, setters, toString implementations
  
}

И HashMap, хранящий Employee в качестве ключей:

1
2
3
4
Map<Employee, Integer> map = new HashMap<>();
  
map.put(new Employee(1, "Sam"), 1);
map.put(new Employee(2, "Sierra"), 2);

Теперь, когда мы вставили две записи, давайте попробуем проверить containsKey () :

1
boolean containsSam = map.containsKey(new Employee(1, "Sam")); //false

Несмотря на то, что у нас есть запись для Сэма , containsKey () вернул false . Это потому, что мы еще не переопределили методы equals () и hashCode () . И по умолчанию equals () просто делает сравнение на основе ссылок.

Переопределение equals () и hashCode () :

Согласно Javadocs:

Когда мы переопределяем метод equals () , мы также должны переопределить метод hashCode ()

Это поможет избежать нарушения контракта equals-hashCode .

Обратите внимание, что компилятор не будет жаловаться, если мы нарушим контракт, но мы можем столкнуться с неожиданным поведением, скажем, когда мы храним такие объекты как ключи в HashMap .

Мы можем быстро переопределить эти методы, используя функцию IDE. При использовании Eclipse мы можем перейти к Source-> Generate hashCode () и equals (). Давайте посмотрим на сгенерированные реализации для нашего класса Employee :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Employee {
  
    ...
      
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
  
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Employee other = (Employee) obj;
        if (id != other.id)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        }
        else if(!name.equals(other.name))
            return false;
  
        return true;
    }
}

Ясно, что одни и те же поля использовались для реализации методов equals () и hashCode (), чтобы не отставать от контракта.

Лучшие практики:

Вот некоторые из лучших практик, которые следует соблюдать при работе с equals () и hashCode () :

  • Реализуйте hashCode () для равномерного распределения элементов по различным сегментам. Идея состоит в том, чтобы минимизировать количество столкновений и, в свою очередь, иметь хорошую производительность
  • Мы должны использовать одинаковые поля для реализаций equals () и hashCode ().
  • Предпочитайте неизменяемые объекты как ключи в HashMap, поскольку они поддерживают кэширование значений хеш-кода
  • При работе с инструментом ORM всегда используйте геттеры вместо полей в определении методов hashCode () и equals () . Это потому, что несколько полей могут быть загружены лениво

Вывод:

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

Оставьте первый комментарий.

Опубликовано на Java Code Geeks с разрешения Шубхры Шриваставы, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Java equals () и hashCode ()

Мнения, высказанные участниками Java Code Geeks, являются их собственными.