Какова роль методов equals и hashCode в HashMap
Что проверяют
Понимаете ли вы механику хеш-таблицы, а не только формулировку контракта. Вопрос популярный, поэтому заученный ответ слышно сразу, и интервьюер уходит вглубь: что будет, если hashCode возвращает константу, и что произойдёт с записью, если у ключа изменить поле после вставки.
Как отвечать
Пройдите по пути put:
- вызывается
hashCode()ключа, затем результат перемешивается операциейh ^ (h >>> 16), чтобы в индексе участвовали и старшие биты; - индекс корзины вычисляется как
hash & (n - 1), гдеnэто длина внутреннего массива, всегда степень двойки, по умолчанию 16; - внутри корзины сначала сравниваются хеши и ссылки, и только при совпадении вызывается
equals. Равный ключ означает замену значения, иначе элемент добавляется в цепочку; - когда в одной корзине набирается 8 элементов, а таблица выросла минимум до 64 ячеек, цепочка превращается в красно-чёрное дерево, при уменьшении до 6 разворачивается обратно;
- при заполнении сверх
capacity * loadFactor(по умолчанию 0.75) таблица удваивается и элементы перераспределяются.
Затем контракт: равные по equals объекты обязаны давать равный hashCode, обратное не требуется, коллизии законны и неизбежны. Сам equals должен быть рефлексивным, симметричным, транзитивным, согласованным, и x.equals(null) всегда false. На практике: Objects.hash(...) и Objects.equals(...), а record генерирует оба метода сам. Ключ стоит делать неизменяемым.
Где спотыкаются
Переопределяют только equals. Объект уходит в одну корзину, ищется в другой, и get возвращает null при внешне равных ключах.
Возвращают из hashCode константу и считают это корректным. Формально контракт не нарушен, фактически мапа превращается в один длинный список, а поиск деградирует до линейного, в лучшем случае до логарифмического после превращения корзины в дерево.
Кладут в ключ изменяемый объект и правят его поля после вставки. Запись остаётся в старой корзине, get и remove её не находят, и она висит в памяти до очистки всей мапы.