Ответ
Чтобы объекты класса корректно работали в качестве ключа в HashMap (или элемента в HashSet, который внутри использует HashMap), класс должен корректно переопределять методы equals(Object) и hashCode(), соблюдая их контракт.
Контракт между equals и hashCode:
- Согласованность: Если два объекта равны согласно
equals(Object), то вызовhashCode()для каждого из них должен возвращать одно и то же целое число. - Обратное не обязательно: Разные хэш-коды гарантируют, что объекты не равны. Но одинаковые хэш-коды (коллизия) не гарантируют равенства объектов.
Неизменяемость (Immutability) — сильная рекомендация: Поля, участвующие в расчетах equals и hashCode, не должны изменяться после того, как объект стал ключом. Если хэш-код изменится, HashMap не сможет найти ключ в исходном бакете, что приведет к потере данных.
Пример корректной реализации:
public final class Person { // final класс для предотвращения нарушения контракта в подклассах
private final String name; // final поля для обеспечения неизменяемости
private final int id;
public Person(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true; // Проверка на один и тот же объект
if (o == null || getClass() != o.getClass()) return false; // Проверка типа
Person person = (Person) o;
return id == person.id && Objects.equals(name, person.name); // Сравнение значимых полей
}
@Override
public int hashCode() {
// Использование Objects.hash() для удобства и соблюдения контракта
return Objects.hash(name, id);
}
}
Почему это важно для HashMap:
- При добавлении пары ключ-значение вычисляется
hashCode()ключа для определения номера бакета (ячейки массива). - Если в бакете уже есть элементы (коллизия), для сравнения используется
equals(Object), чтобы проверить, не равен ли новый ключ уже существующему. Без корректной реализации этих методов поиск, вставка и удаление элементов будут работать неправильно.