Какой контракт между методами hashCode() и equals() должен соблюдаться для корректной работы ключей в HashMap?

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

Ответ

Для корректной работы объекта в качестве ключа HashMap между его методами hashCode() и equals() должен соблюдаться следующий контракт:

  1. Согласованность: Если a.equals(b) возвращает true, то a.hashCode() должен быть равен b.hashCode().
  2. Обратное не обязательно: Равные хеш-коды (a.hashCode() == b.hashCode()) не гарантируют, что a.equals(b) == true. Это коллизия.
  3. Неизменность: Значения, возвращаемые hashCode() и результат equals(), должны быть постоянными для объекта на протяжении его жизни (при условии, что поля, участвующие в сравнении, не изменяются).

Почему это важно для HashMap? При добавлении элемента map.put(key, value):

  1. Вычисляется hashCode() ключа для определения индекса корзины (bucket).
  2. Если в корзине уже есть элементы (коллизия), для каждого из них вызывается equals(key) для проверки, является ли ключ дубликатом.

Нарушение контракта приводит к ошибкам:

  • Если equals говорит, что ключи одинаковы, но hashCode разный, дубликат ключа будет помещен в другую корзину, что нарушит уникальность ключей.
  • Объект, помещенный в мапу как ключ, после изменения своих полей может стать «ненаходимым», так как изменится его hashCode().

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

public class Person {
    private final String id;
    private String name;

    public Person(String id, String name) {
        this.id = id;
        this.name = name;
    }

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

    @Override
    public int hashCode() {
        return Objects.hash(id); // Хешируем только по id
    }
}