Що станеться, якщо змінити змінний ключ в 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]]