Короткий ответ
Контракт Java: если два объекта равны по equals(), их hashCode() обязаны быть одинаковыми. Обратное не требуется: одинаковый хэш-код не гарантирует равенства — это коллизия. Связь критична для HashMap и HashSet: по hashCode определяется корзина, а точное совпадение внутри корзины проверяется через equals.
Как это работает подробнее
Если переопределить только equals и не тронуть hashCode, контракт нарушается: равные объекты разъедутся по разным корзинам, поиск не найдёт элемент, а в Set или Map появятся дубликаты. Поэтому методы переопределяют всегда вместе и на основе одного и того же набора значимых полей. Требования к equals: рефлексивность (объект равен себе), симметричность, транзитивность, консистентность и возврат false для null.
- Генерация: используйте средства IDE или
Objects.equalsиObjects.hash— это синхронизирует поля в обоих методах. - Типичный баг: нарушение контракта — одна из самых частых причин неочевидных ошибок при работе с коллекциями.
Пример кода
Student.java
import java.util.Objects;
public class Student {
private final String name;
private final int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student s = (Student) o;
return age == s.age && Objects.equals(name, s.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // те же поля, что в equals
}
}Что использовать на практике
- Переопределяйте методы парой и на одних значимых полях:
equals— черезObjects.equals,hashCode— черезObjects.hash. - Для хэша берите только неизменяемые поля, иначе после изменения объекта хэш «поплывёт» и элемент потеряется в коллекции.
- Проверяйте поведение: равные объекты должны находиться в HashSet и работать ключами HashMap.
Подводные камни
- Разный
hashCodeу равных объектов (переопределили только equals) ломает поиск в HashMap и HashSet. - Равный
hashCodeу разных объектов — это нормальная коллизия: она влияет лишь на длину цепочки в бакете. - Сравнение через
getClass()в equals делает подклассы не равными родителю — обычно это желаемое поведение, но помните о нём.
Как отвечать на собеседовании
- Сформулируйте контракт: равенство по equals влечёт равенство hashCode, но не наоборот → объясните роль пары в HashMap: корзина по hashCode, сверка через equals внутри корзины → опишите последствия нарушения (объекты «теряются», дубликаты в Set) → перечислите требования к equals → завершите практикой: переопределять методы вместе на одних полях, лучше через IDE или Objects.equals и Objects.hash.