Что произойдёт, если изменить мутабельный ключ в HashMap?
Если изменить объект, который используется как ключ в HashMap, он потеряется — вы не сможете его найти.
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 — не нашли!
Почему
- При
putHashMap вычислила хеш["A"]→ положила в бакет №5 - После изменения ключа хеш
["A", "B"]→ другой бакет getищет в неправильном бакете →null
Объект всё ещё лежит в карте, но вы не можете его достать.
Middle Level
Механика сбоя
- put(key, value) — хеш ключа → бакет №5
- Мутация — изменили поле ключа → новый хеш
- 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
Как избежать
- Иммутабельные ключи —
String,UUID,Record— всегда - Удалить → изменить → добавить:
V value = map.remove(key); key.mutate(); map.put(key, value); - IdentityHashMap — если мутация неизбежна и вы не контролируете объект (сравнение по
==)Map<List<String>, String> map = new IdentityHashMap<>(); // Сравнение по ==, а не по equals — мутация не ломает lookup, пока объект тот же.
Глубинная проблема
Изменение мутабельного ключа нарушает инвариант HashMap: hashCode ключа должен быть стабильным. Это не баг HashMap, а нарушение контракта hashCode() — если equals не изменился, то hashCode должен быть тем же, но мутация меняет и то, и другое.
Резюме для Senior
- Изменение мутабельного ключа “ломает” карту — объект недоступен для
get() - Это классический memory leak и источник трудноуловимых багов
- Всегда проектируйте ключи как неизменяемые сущности
- Если мутация необходима:
remove→mutate→put 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]]