Какие требования предъявляются к ключам в HashMap в Java
Что проверяют
Понимаете ли вы контракт equals и hashCode и что происходит, когда его нарушают. Вопрос базовый, но отсеивает многих.
Как отвечать
Формальных требований к типу ключа нет, но есть контракт, без которого карта работает неправильно:
- equals и hashCode согласованы: равные объекты обязаны давать одинаковый хеш. Обратное неверно, разные объекты могут совпасть по хешу, это коллизия, и она допустима;
- hashCode стабилен: пока объект лежит в карте, его хеш не должен меняться. Отсюда практическое правило про неизменяемость ключа;
- equals рефлексивно, симметрично, транзитивно и не меняется со временем.
Что происходит при нарушении. Положили ключ, потом изменили поле, участвующее в hashCode. Хеш стал другим, поиск идёт в другую корзину, и get возвращает null, хотя объект физически в карте лежит. Он становится недостижимым и вдобавок не удаляется.
Отсюда практика: ключом берут неизменяемые типы (String, Integer, enum, record с неизменяемыми полями). null в качестве ключа разрешён ровно один, он кладётся в нулевую корзину, а вот ConcurrentHashMap его уже не принимает.
Полезно добавить про устройство: массив корзин, коллизии в списке, а с восьми элементов и при размере таблицы от 64 список превращается в красно-чёрное дерево. Поэтому от hashCode требуется ещё и хорошее распределение: константа сведёт карту к списку и даст O(n).
Где спотыкаются
Переопределяют equals и забывают hashCode. Объект перестаёт находиться в карте сразу же.
Считают, что одинаковый хеш означает равенство.
Берут ключом изменяемый объект, например список, и получают потерянные значения.
Не знают, что record генерирует и equals, и hashCode по всем компонентам, и пишут их руками.