Вопрос 28 · Раздел 13

Что произойдёт, если изменить мутабельный ключ в HashMap?

Если изменить объект, который используется как ключ в HashMap, он потеряется — вы не сможете его найти.

Версии по языкам: English Russian Ukrainian

Junior Level

Если изменить объект, который используется как ключ в HashMap, он потеряется — вы не сможете его найти.

List<String> key = new ArrayList<>(List.of("A"));
Map<List<String>, String> map = new HashMap<>();
map.put(key, "value");

key.add("B"); // Изменили ключ!
System.out.println(map.get(key)); // null — не нашли!

Почему

  • При put HashMap вычислила хеш ["A"] → положила в бакет №5
  • После изменения ключа хеш ["A", "B"] → другой бакет
  • get ищет в неправильном бакете → null

Объект всё ещё лежит в карте, но вы не можете его достать.


Middle Level

Механика сбоя

  1. put(key, value) — хеш ключа → бакет №5
  2. Мутация — изменили поле ключа → новый хеш
  3. get(key) — новый хеш → бакет №10 → пусто → null

Физически объект всё ещё в бакете №5, но стал недостижимым.

Последствия

Логическая недоступность: объект нельзя получить через get() или удалить через remove() по изменённому ключу. Он занимает место до очистки всей карты или перебора entrySet().

Дубликаты в HashSet:

Set<List<String>> set = new HashSet<>();
set.add(new ArrayList<>(List.of("A")));
// Изменили элемент внутри списка
set.add(new ArrayList<>(List.of("A"))); // Добавится снова!

Как найти потерянный объект

Через полный перебор — O(n) вместо O(1):

for (var entry : map.entrySet()) {
    if (entry.getKey().contains("A")) {
        // нашли
    }
}

Senior Level

Как избежать

  1. Иммутабельные ключиString, UUID, Record — всегда
  2. Удалить → изменить → добавить:
    V value = map.remove(key);
    key.mutate();
    map.put(key, value);
    
  3. IdentityHashMap — если мутация неизбежна и вы не контролируете объект (сравнение по ==)
    Map<List<String>, String> map = new IdentityHashMap<>();
    // Сравнение по ==, а не по equals — мутация не ломает lookup, пока объект тот же.
    

Глубинная проблема

Изменение мутабельного ключа нарушает инвариант HashMap: hashCode ключа должен быть стабильным. Это не баг HashMap, а нарушение контракта hashCode() — если equals не изменился, то hashCode должен быть тем же, но мутация меняет и то, и другое.

Резюме для Senior

  • Изменение мутабельного ключа “ломает” карту — объект недоступен для get()
  • Это классический memory leak и источник трудноуловимых багов
  • Всегда проектируйте ключи как неизменяемые сущности
  • Если мутация необходима: removemutateput
  • IdentityHashMap — крайний случай, когда контроль над ключом невозможен

🎯 Шпаргалка для интервью

Обязательно знать:

  • Изменение мутабельного ключа → новый hashCode → get ищет в другом бакете → null
  • Объект физически в карте, но стал недостижимым — логическая утечка памяти
  • Дубликаты в HashSet: изменённый объект добавится снова как “новый”
  • Найти потерянный объект можно только через entrySet перебор — O(n) вместо O(1)
  • Как избежать: иммутабельные ключи всегда, или remove → mutate → put
  • IdentityHashMap — сравнивает по ==, мутация не ломает lookup (пока объект тот же)

Частые уточняющие вопросы:

  • Объект удаляется из карты? — Нет, остаётся в старом бакете, но недоступен через get/remove
  • Как найти потерянный объект? — Перебор entrySet — O(n)
  • IdentityHashMap решает проблему? — Да, сравнивает по ссылке, но это специфический use case
  • Это баг HashMap? — Нет, нарушение контракта: hashCode должен быть стабильным

Красные флаги (НЕ говорить):

  • «Объект удалится из карты» — нет, остаётся но недоступен
  • «HashMap сама исправит hashCode» — нет, это контракт разработчика
  • «Мутабельный ключ — это нормально» — классический source of memory leaks и bugs
  • IdentityHashMap — замена HashMap» — это совершенно другая структура, сравнивает по ссылке

Связанные темы:

  • [[27. Можно ли использовать иммутабельные объекты как ключи в HashMap]]
  • [[22. В чём преимущества иммутабельных объектов для кэширования]]
  • [[20. Что такое Record и как он помогает создавать иммутабельные классы]]
  • [[14. В чём разница между shallow copy и deep copy]]