Методы equals() и hashCode() находятся в классе Object. Таким образом, каждый класс в Java получает реализацию по умолчанию для этих методов. В этом посте мы подробно рассмотрим методы equals() и hashCode() в Java.
Java equals()
Метод equals() в классе Object определен следующим образом:
public boolean equals(Object obj) {
return (this == obj);
}
Согласно документации по Java, любая реализация должна выполнять следующие принципы.
- Для любого объекта x,
x.equals(x)
должен возвращатьtrue
. - Для любых двух объектов x и y,
x.equals(y)
должен возвращатьtrue
только еслиy.equals(x)
возвращаетtrue
. - Для нескольких объектов x, y и z, если
x.equals(y)
возвращаетtrue
иy.equals(z)
возвращаетtrue
, тоx.equals(z)
должен возвращатьtrue
. - Несколько вызовов
x.equals(y)
должны возвращать один и тот же результат, если ни одно из свойств объекта, используемых в реализации методаequals()
, не изменяется. - Реализация метода equals() в классе Object возвращает
true
только если оба ссылки указывают на один и тот же объект.
Java hashCode()
Метод Java Object hashCode() является нативным и возвращает целочисленное значение хеш-кода объекта. Общий контракт метода hashCode() таков:
- Повторные вызовы hashCode() должны возвращать одно и то же целочисленное значение, если только свойство объекта, используемое в методе equals(), не было изменено.
- Значение хеш-кода объекта может измениться при многократном выполнении одного и того же приложения.
- Если два объекта равны согласно методу equals(), то их хеш-коды должны быть одинаковыми.
- Если два объекта неравны согласно методу equals(), их хеш-коды не обязаны быть разными. Их значения хеш-кода могут быть равными или не равными.
Важность методов equals() и hashCode()
Методы hashCode() и equals() в Java используются в реализациях на основе хеш-таблиц для хранения и извлечения данных. Я подробно объяснил это в статье Как работает HashMap в Java? Реализация методов equals() и hashCode() должна следовать этим правилам.
- Если
o1.equals(o2)
, тоo1.hashCode() == o2.hashCode()
всегда должно бытьtrue
. - Если
o1.hashCode() == o2.hashCode
истинно, это не означает, чтоo1.equals(o2)
будетtrue
.
Когда переопределять методы equals() и hashCode()?
Когда мы переопределяем метод equals(), почти всегда необходимо также переопределить метод hashCode(), чтобы наша реализация не нарушала их контракт. Обратите внимание, что ваша программа не будет выбрасывать исключения, если контракт equals() и hashCode() нарушен, если вы не планируете использовать класс в качестве ключа хэш-таблицы, это не вызовет проблем. Если вы планируете использовать класс в качестве ключа хэш-таблицы, то обязательно нужно переопределить и методы equals() и hashCode(). Посмотрим, что произойдет, когда мы полагаемся на реализацию по умолчанию методов equals() и hashCode() и используем пользовательский класс в качестве ключа HashMap.
package com.journaldev.java;
public class DataKey {
private String name;
private int id;
// методы getter и setter
@Override
public String toString() {
return "DataKey [name=" + name + ", id=" + id + "]";
}
}
package com.journaldev.java;
import java.util.HashMap;
import java.util.Map;
public class HashingTest {
public static void main(String[] args) {
Map<DataKey, Integer> hm = getAllData();
DataKey dk = new DataKey();
dk.setId(1);
dk.setName("Pankaj");
System.out.println(dk.hashCode());
Integer value = hm.get(dk);
System.out.println(value);
}
private static Map<DataKey, Integer> getAllData() {
Map<DataKey, Integer> hm = new HashMap<>();
DataKey dk = new DataKey();
dk.setId(1);
dk.setName("Pankaj");
System.out.println(dk.hashCode());
hm.put(dk, 10);
return hm;
}
}
При выполнении вышеуказанной программы будет выведено null
. Это происходит потому, что для поиска корзины для ключа используется метод hashCode() объекта. Поскольку у нас нет доступа к ключам HashMap, и мы создаем ключ снова для извлечения данных, вы заметите, что хэш-коды обоих объектов различны, и, следовательно, значение не найдено.
Реализация методов equals() и hashCode()
Мы можем определить свою собственную реализацию методов equals() и hashCode(), но если мы не будем делать это осторожно, это может вызвать странные проблемы во время выполнения. К счастью, большинство сред разработки в наши дни предоставляют способы автоматической реализации этих методов, и, если нужно, мы можем изменить их в соответствии с нашими требованиями. Мы можем использовать Eclipse для автоматической генерации методов equals() и hashCode(). Вот автоматически сгенерированные реализации методов equals() и hashCode().
@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;
DataKey other = (DataKey) 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() используют одни и те же поля для вычислений, чтобы их контракт оставался действительным. Если вы снова запустите тестовую программу, мы получим объект из карты, и программа выведет 10. Мы также можем использовать Project Lombok, чтобы автоматически генерировать реализации методов equals и hashCode.
Что такое столкновение хешей
Простыми словами, реализации хеш-таблиц в Java используют следующую логику для операций get и put.
- Сначала определите “ведро” для использования, используя хеш-код “ключа”.
- Если в ведре нет объектов с тем же хеш-кодом, то добавьте объект для операции put и верните null для операции get.
- Если в корзине есть другие объекты с тем же хеш-кодом, вступает в игру метод “equals”.
- Если equals() возвращает true, и это операция добавления, то значение объекта переопределяется.
- Если equals() возвращает false, и это операция добавления, то новая запись добавляется в корзину.
- Если equals() возвращает true, и это операция получения, то возвращается значение объекта.
- Если equals() возвращает false, и это операция получения, то возвращается null.
На изображении ниже показаны элементы корзины HashMap и их связь с методами equals() и hashCode(). Явление, когда два ключа имеют одинаковый хеш-код, называется коллизией хеша. Если метод hashCode() не реализован правильно, возможно большее количество коллизий, и записи в карте не будут правильно распределены, что вызовет замедление операций получения и добавления. Это причина использования простых чисел при генерации хеш-кода, чтобы записи в карте правильно распределялись по всем корзинам.
Что, если мы не реализуем и hashCode(), и equals()?
Мы уже видели выше, что, если не реализовать hashCode(), мы не сможем получить значение, потому что HashMap использует хеш-код для поиска корзины, в которой находится запись. Если мы используем только hashCode() и не реализуем equals(), то также значение не будет получено, потому что метод equals() вернет false.
Лучшие практики при реализации методов equals() и hashCode()
- Используйте одни и те же свойства в реализациях методов equals() и hashCode(), чтобы их контракт не нарушался при обновлении любого свойства.
- Лучше использовать неизменяемые объекты в качестве ключа хеш-таблицы, чтобы мы могли кешировать хеш-код, а не вычислять его при каждом вызове. Поэтому строка является хорошим кандидатом для ключа хеш-таблицы, потому что она неизменяема и кеширует значение хеш-кода.
- Реализуйте метод hashCode() так, чтобы количество коллизий было минимальным, и записи равномерно распределялись по всем корзинам.
Вы можете загрузить полный код с нашего репозитория на GitHub Repository.
Source:
https://www.digitalocean.com/community/tutorials/java-equals-hashcode