Расскажи про Hash Code & Equals Contract
hashCode() и equals() — это методы класса Object, которые определяют, как объекты сравниваются и как вычисляется их хеш-код. Контракт между ними гласит: если два объекта равны по equals(), их hashCode() должны возвращать одинаковое значение. Обратное не требуется: разные объекты могут иметь одинаковый хеш-код (это называется коллизией). Правильное переопределение этих методов критически важно для корректной работы хеш-коллекций, таких как HashSet, HashMap и Hashtable.
Контракт equals() и hashCode()
Контракт определяет обязательные правила взаимодействия методов:
- Согласованность: если
x.equals(y)возвращаетtrue, тоx.hashCode() == y.hashCode()обязательно. - Обратное не требуется: если
x.hashCode() == y.hashCode(), это не гарантируетx.equals(y). Разные объекты могут иметь одинаковый хеш-код, что приводит к коллизиям. - Равные объекты — одинаковый хеш-код: это обеспечивает корректный поиск в хеш-таблицах: объект с одинаковым содержимым будет найден в той же «корзине».
Требования к equals()
Метод equals() по умолчанию сравнивает ссылки, поэтому для сравнения по содержимому его переопределяют. При переопределении необходимо соблюдать пять свойств:
- Рефлексивность:
x.equals(x)всегдаtrueдля ненулевогоx. - Симметричность:
x.equals(y)равноy.equals(x). - Транзитивность: если
x.equals(y)иy.equals(z), тоx.equals(z). - Консистентность: при неизменных полях результат
equals()не меняется. - Сравнение с
null:x.equals(null)всегдаfalse.
Пример реализации
public class Person {
private String name;
private int age;
@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 &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}Здесь equals() сравнивает name и age, а hashCode() использует те же поля через Objects.hash(). Это гарантирует, что равные объекты имеют одинаковый хеш-код.
Подводные камни
- Нарушение контракта: если переопределить только
equals()безhashCode(), объекты могут «потеряться» вHashMapилиHashSet— поиск по ключу не найдёт их, даже если они равны. - Использование изменяемых полей: если поля, участвующие в
hashCode(), изменяются после добавления объекта в коллекцию, хеш-код меняется, и объект становится недоступным для поиска. Лучше использовать неизменяемые поля. - Разные типы: в
equals()часто используютgetClass() != o.getClass()для строгой типизации, но это нарушает симметричность при наследовании. Альтернатива —instanceofс проверкой полей, но это требует аккуратности.
Коротко
- Контракт: равные объекты обязаны иметь одинаковый
hashCode(), но одинаковый хеш-код не гарантирует равенства. - Переопределяя
equals(), соблюдай рефлексивность, симметричность, транзитивность, консистентность и сравнение сnull. - Всегда переопределяй
hashCode()вместе сequals(), используя те же значимые поля. - Для генерации хеш-кода удобно использовать
Objects.hash()илиObjects.hashCode()для отдельных полей.
