Какие контракты существуют для работы со справочником
Что проверяют
Формулировка размытая, но почти всегда речь о Map, справочнике «ключ-значение», и о том, какие обязательства должен выполнять ключ. Интервьюер ждёт контракт equals и hashCode, а следом спрашивает, что будет, если изменить ключ после put. Сомневаетесь в смысле вопроса, коротко уточните.
Как отвечать
Главное это связка equals и hashCode:
- равные объекты обязаны иметь равный
hashCode. Обратное не требуется, совпадение хэшей у разных объектов это коллизия; equalsрефлексивен, симметричен, транзитивен, согласован при повторных вызовах и возвращаетfalseдляnull;hashCodeстабилен, пока не меняются поля, участвующие вequals.
HashMap по хэшу выбирает корзину, а внутри ищет ключ через equals. Если в корзине больше 8 элементов и таблица не меньше 64 ячеек, список превращается в красно-чёрное дерево, и поиск при массовых коллизиях деградирует до O(log n), а не до O(n).
У TreeMap свой контракт: ключи сравниваются через compareTo или переданный Comparator, equals не вызывается. Сравнение должно быть согласовано с equals. Пример расхождения: new BigDecimal("1.0") и new BigDecimal("1.00") не равны по equals, но compareTo возвращает 0, поэтому HashSet хранит оба, а TreeSet один.
Назовите интерфейсы: Map, SortedMap, NavigableMap, SequencedMap из Java 21 с firstEntry и reversed, ConcurrentMap с атомарными putIfAbsent и computeIfAbsent. HashMap допускает один null-ключ, ConcurrentHashMap и Map.of не допускают, а Map.of ещё и неизменяем. Лучшие ключи неизменяемы: String или record, у которого equals и hashCode генерируются из компонентов.
Где спотыкаются
Переопределяют equals без hashCode. Равные объекты попадают в разные корзины, и get возвращает null.
Меняют поле ключа после put. Запись остаётся в старой корзине и становится недоступной, хотя size() её учитывает.
Советуют лечить коллизии переходом на ConcurrentHashMap. Он решает многопоточность, а не качество хэш-функции.