Что происходит если hashCode объекта меняется после помещения в Map
Что проверяют
Знаете ли вы контракт hashCode и equals и понимаете ли, что хеш-таблица считает корзину один раз. На Android это не теория: изменяемые модели в роли ключей и элементов множеств встречаются в каждом втором проекте.
Как отвечать
Корзина выбирается в момент put по текущему хешу. Карта не подписана на изменения ключа и ничего не пересчитывает, поэтому запись остаётся там, где её положили. Когда вы потом зовёте get с тем же объектом, хеш считается заново, поиск идёт в другую корзину и возвращается null.
Дальше важно перечислить последствия целиком:
containsKeyотвечаетfalse,removeне удаляет, затоsize()запись считает, и обходentrySetеё отдаёт;- запись становится недостижимой по ключу и при этом занимает память, то есть получается утечка внутри живой коллекции;
- баг плавающий: индекс это маска по младшим битам, и новый хеш иногда попадает в ту же корзину, тогда всё «работает».
Вывод, который от вас ждут: ключ должен быть неизменяемым по тем полям, что участвуют в hashCode и equals. В Kotlin это особенно легко нарушить, потому что data class генерирует hashCode по всем свойствам основного конструктора, и data class с var в роли ключа HashMap или элемента HashSet это готовая мина. Практичнее ключевать стабильным идентификатором, а не самой моделью.
Где спотыкаются
Думают, что карта сама переложит запись в новую корзину. Механизма для этого просто нет, HashMap не знает о мутациях ключа.
Кладут в HashSet изменяемую модель экрана, потом меняют у неё поле и обнаруживают дубли: множество содержит два элемента, которые по equals равны.
Переопределяют equals без hashCode или считают hashCode по полю, которое меняется после загрузки данных. Те же грабли ловятся в кэшах и в наборах выбранных элементов списка.