Какие свойства нужно переопределить в классе ключа для корректной работы с HashMap и какие правила должны соблюдаться при переопределении equals и hashCode
Что проверяют
Знаете ли вы контракт equals и hashCode и можете ли объяснить, что ломается внутри HashMap при его нарушении. Следом почти наверняка спросят: «Что будет, если переопределить только equals?» или «А если поменять поле ключа после put?»
Как отвечать
Переопределяются два метода, equals(Object) и hashCode(), всегда вместе. Объясните, как ими пользуется HashMap: по хэшу выбирается корзина (старшие биты перемешиваются с младшими, индекс равен (n - 1) & hash), а внутри корзины ключ ищется сравнением хэша и затем equals.
Контракт equals:
- рефлексивность:
x.equals(x)истинно; - симметричность: если
x.equals(y), то иy.equals(x); - транзитивность: если
xравенy, аyравенz, тоxравенz; - согласованность: пока поля не меняются, результат тот же;
- null:
x.equals(null)возвращаетfalse, без исключения.
Контракт hashCode: равные объекты дают равный хэш, результат стабилен, пока не меняются поля из equals. Разные объекты могут совпасть по хэшу, это допустимая коллизия.
Практика: поля ключа делайте final и используйте в обоих методах один набор полей. Удобны Objects.equals для сравнения с учётом null и Objects.hash. С Java 16 ключ проще объявить как record, методы сгенерируются по компонентам. Про коллизии добавьте: с Java 8 цепочка, переросшая порог TREEIFY_THRESHOLD = 8, превращается в красно-чёрное дерево, если таблица не меньше 64, иначе таблица просто расширяется. Худший случай поиска становится близок к O(log n), особенно если ключ реализует Comparable.
Где спотыкаются
Переопределяют только equals. Равные ключи попадают в разные корзины, get возвращает null, а в карте появляются логические дубли.
Пишут equals(Key other) вместо equals(Object). Это перегрузка, а не переопределение, и HashMap её не вызовет. Аннотация @Override ловит такую ошибку при компиляции.
Меняют поле ключа, уже лежащего в карте. Хэш меняется, запись остаётся в старой корзине, найти её больше нельзя. И record с массивом внутри сравнивает массив по ссылке, так что корректности сам по себе не гарантирует.