Зачем в Java методы `hashCode()` и `equals()` должны быть переопределены согласованно?

«Зачем в Java методы `hashCode()` и `equals()` должны быть переопределены согласованно?» — вопрос из категории Основы программирования, который задают на 10% собеседований QA Тестировщик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Методы hashCode() и equals() работают в паре для обеспечения корректной работы объектов в хеш-ориентированных коллекциях, таких как HashMap, HashSet и Hashtable. Нарушение контракта между ними приводит к трудноуловимым ошибкам.

Основной контракт (согласно JavaDoc Object):

  1. Если два объекта равны согласно equals(Object), то их hashCode() должны возвращать одинаковое целочисленное значение.
  2. Обратное необязательно: разные объекты (по equals) могут иметь одинаковый хеш-код (коллизия). Это нормально, но снижает производительность.

Что происходит при нарушении контракта? Объект, помещённый в HashMap, может быть не найден при поиске по эквивалентному ключу, так как коллекция сначала ищет корзину по hashCode(), и только затем сравнивает ключи внутри корзины через equals().

Пример корректной реализации:

public class User {
    private final Long id;
    private final String email;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id); // Сравниваем только по id
    }

    @Override
    public int hashCode() {
        return Objects.hash(id); // Используем те же поля, что и в equals()
    }
}

Использование Objects.hash() и Objects.equals() упрощает соблюдение контракта.