Могут ли элементы потеряться в HashMap
Что проверяют
Знаете ли вы контракт equals и hashCode и устройство HashMap настолько, чтобы отличить реальные причины «потери» от мифов. Следом обычно спрашивают, почему HashMap опасен в нескольких потоках и чем его заменить.
Как отвечать
Сразу оговорите: сама структура данные не теряет, коллизии и расширение обрабатываются корректно. Элементы теряются из-за того, как ею пользуются:
- изменяемый ключ: ключ положили, потом поменяли поле, участвующее в
hashCode(). Запись осталась в старой корзине,get()иcontainsKey()ищут в новой и возвращаютnull. При этом элемент виден при обходе и вsize(), это ещё и утечка памяти; - нарушен контракт:
equals()переопределён, аhashCode()нет. Равные по смыслу ключи попадают в разные корзины, получаются дубли и промахи при поиске; - запись из нескольких потоков: две параллельные вставки в одну корзину, и одна затирает другую,
size()расходится с содержимым. До Java 8 параллельное расширение могло зациклить список в корзине и повеситьget(). С Java 8 при переносе порядок узлов сохраняется и цикла нет, но потерянные обновления остались.
Для полноты назовите механику. При заполнении выше loadFactor, по умолчанию 0.75, таблица удваивается. Корзина превращается в красно-чёрное дерево, когда элементов в ней становится больше восьми, а ёмкость таблицы не меньше 64, иначе таблица просто расширяется.
Решения: неизменяемые ключи, лучше всего record или String; equals() и hashCode() из одних и тех же полей; для многопоточности ConcurrentHashMap и атомарные compute() и merge() вместо пары get() плюс put().
Где спотыкаются
Говорят, что при коллизии один элемент перезаписывает другой. Нет: оба ключа живут в одной корзине, перезапись происходит только при равенстве по equals().
Путают HashMap с WeakHashMap и утверждают, что сборщик мусора может удалить запись. Обычный HashMap держит ключ сильной ссылкой, записи исчезают только в WeakHashMap, и это его назначение.
Лечат многопоточность через Collections.synchronizedMap() и забывают, что обход такой карты нужно синхронизировать вручную, а «проверить и положить» остаётся неатомарным.