Может ли ключом в HashMap в Java быть другой Map
Что проверяют
Знаете ли вы контракт equals() и hashCode() у коллекций и понимаете ли, почему изменяемый ключ ломает HashMap. Уточняют обычно так: что будет, если после put добавить элемент в карту-ключ.
Как отвечать
Технически может, ни компилятор, ни HashMap не против. Интереснее, как это работает:
equals(): интерфейсMapтребует сравнения по содержимому. Две карты равны, если у них одинаковые пары ключ-значение, даже если однаHashMap, а другаяTreeMap;hashCode(): по контракту это сумма хешей всех записей, а хеш записи равенkey.hashCode() ^ value.hashCode(). Так считаетAbstractMap, адрес объекта тут ни при чём;- стоимость: хеш не кешируется и считается обходом всей карты при каждом
get()иput(), то есть за O(n) от размера ключа.
Главная проблема в изменяемости. HashMap кладёт запись в корзину по хешу, вычисленному при вставке. Если потом поменять карту-ключ, хеш изменится, get() и containsKey() начнут возвращать null и false, а запись останется в таблице и найдётся только перебором. Это и потерянные данные, и утечка памяти.
Как сделать правильно:
- неизменяемая копия:
Map.copyOf(source)из Java 10 илиMap.of(...). Учтите, что они не принимаютnull; - не
unmodifiableMap:Collections.unmodifiableMap()только обёртка, исходную карту по-прежнему можно менять; - свой тип ключа: чаще всего лучше
record FilterKey(String city, int level). У recordequals()иhashCode()генерируются из компонентов, а смысл ключа виден из типа.
Где спотыкаются
Говорят, что hashCode() у карты по умолчанию возвращает адрес объекта. Это неправда, все стандартные реализации Map считают его по содержимому.
Не упоминают изменяемость, хотя именно её ждёт интервьюер. Ответ «можно, если переопределены методы» без этой оговорки считается неполным.
Забывают про рекурсию. Спецификация Map прямо запрещает карте содержать саму себя ключом, а карта, которая содержит себя значением, уронит hashCode() с StackOverflowError.