Какими свойствами должна обладать коллекция для использования в качестве ключа в хэш-таблице
Что проверяют
Знаете ли вы контракт equals/hashCode и что происходит с ключом, который изменили после вставки в HashMap. Следом спрашивают, можно ли сделать ключом массив и почему data class с полем-массивом ведёт себя странно.
Как отвечать
Коллекция годится в ключ при трёх условиях:
- равенство по содержимому: у
List,SetиMapэто прописано в контракте интерфейса. Списки равны при равных элементах в том же порядке, множества при одинаковом составе, независимо от реализации. Хэш списка зависит от порядка, хэш множества нет; - корректные элементы: хэш коллекции собирается из хэшей элементов, значит, элементы тоже обязаны честно реализовать
equals/hashCode. В Kotlin это обычноdata class, строки и числа; - неизменность, пока ключ лежит в таблице:
HashMapкладёт запись в бакет по хэшу, посчитанному при вставке. Добавили элемент в список-ключ, хэш изменился, иgetищет в другом бакете: запись в карте есть, но найти её по ключу нельзя.
В Kotlin важно уточнить: listOf() возвращает read-only интерфейс, а не неизменяемый объект. Если снаружи осталась ссылка на исходный MutableList, его можно поменять. Надёжнее класть в ключ копию через toList() или брать persistentListOf из kotlinx.collections.immutable.
С массивами иначе: IntArray и Array<T> сравниваются по ссылке, и два массива с одинаковым содержимым дадут два разных ключа. Нужна обёртка с contentEquals/contentHashCode или переход на List. По той же причине некорректно сравнивается data class с полем-массивом, и IDE об этом предупреждает.
Есть и цена: hashCode списка считается проходом по всем элементам при каждом обращении и, в отличие от String, не кэшируется. Для длинной коллекции дешевле завести отдельный ключ-идентификатор.
Где спотыкаются
Говорят «коллекция должна быть неизменяемой» и не объясняют, что ломается: хэш, посчитанный при вставке, перестаёт совпадать с текущим.
Считают listOf гарантией неизменяемости. Это ограничение интерфейса, а не свойство объекта.
Забывают про порядок: думают, что listOf(1, 2) и listOf(2, 1) дадут один ключ. Если порядок не важен, нужен Set.