Почему не рекомендуется использовать изменяемый (mutable) объект в качестве ключа в HashMap?

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

Ответ

Использование изменяемого объекта в качестве ключа HashMapопасная практика, которая приводит к потере данных. Проблема возникает из-за нарушения контракта между hashCode(), equals() и состоянием объекта.

Механизм проблемы:

  1. Объект добавляется в HashMap. Его хэш-код (вычисленный hashCode()) определяет корзину (bucket) для размещения.
  2. После добавления изменяется поле объекта, которое участвует в вычислении hashCode() и/или equals().
  3. При попытке найти объект по ключу map.get(key) вычисляется новый хэш-код. Он указывает на другую корзину, где объект не хранится. В результате возвращается null — объект становится "потерянным".

Наглядный пример:

class MutableKey {
    private String id;
    // Конструктор, геттер, сеттер

    @Override
    public int hashCode() { return id.hashCode(); }
    @Override
    public boolean equals(Object o) { /* сравнение по id */ }
}

MutableKey key = new MutableKey("A-101");
Map<MutableKey, String> map = new HashMap<>();
map.put(key, "Data for A-101");

System.out.println(map.get(key)); // Вывод: "Data for A-101"

key.setId("B-202"); // КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ!
System.out.println(map.get(key)); // Вывод: null - объект не найден!

Лучшие практики:

  1. Используйте неизменяемые (immutable) объекты в качестве ключей (например, String, Integer, собственные классы с final полями).
  2. Если объект должен быть изменяемым, гарантируйте, что поля, участвующие в hashCode()/equals(), никогда не меняются после добавления в HashMap.
  3. В качестве ключа часто лучше использовать неизменяемый уникальный идентификатор (например, userId, uuid).