Ответ
Контракт (соглашение), определенный в документации Java, гласит:
- Согласованность во времени: Если
equals()для двух объектов возвращаетtrue, то вызовhashCode()для каждого из них должен возвращать одно и то же целочисленное значение. При повторных вызовах на неизмененном объектеhashCode()должен возвращать то же значение. - Обратное не обязательно: Если
hashCode()для двух объектов совпадает, это не гарантирует, чтоequals()вернетtrue(это коллизия хэша).
Зачем это нужно?
Этот контракт критически важен для корректной работы hash-базированных коллекций: HashMap, HashSet, Hashtable. Они используют хэш-код для быстрого определения «корзины» (bucket), в которой может находиться объект.
Нарушение контракта приводит к ошибкам:
class BrokenPerson {
private String name;
private int age;
public BrokenPerson(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;
BrokenPerson that = (BrokenPerson) o;
return age == that.age && Objects.equals(name, that.name);
}
// НЕТ переопределения hashCode()! Используется реализация Object.hashCode()
}
// Проблема в использовании:
Set<BrokenPerson> set = new HashSet<>();
BrokenPerson p1 = new BrokenPerson("Alice", 30);
BrokenPerson p2 = new BrokenPerson("Alice", 30);
System.out.println(p1.equals(p2)); // true (логически равны)
set.add(p1);
System.out.println(set.contains(p2)); // Может вернуть FALSE! Объекты попали в разные корзины.
Правильная реализация:
Всегда переопределяйте hashCode(), если переопределяете equals(). Используйте одни и те же значимые поля в обоих методах. Удобно делать это с помощью Objects.hash().
class CorrectPerson {
private String name;
private int age;
public CorrectPerson(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;
CorrectPerson that = (CorrectPerson) o;
return age == that.age && Objects.equals(name, that.name); // Поля для equals
}
@Override
public int hashCode() {
return Objects.hash(name, age); // Те же поля, что и в equals()!
}
}