Равенство и хеширование
В Java «равны» — это два разных вопроса. == спрашивает про идентичность: тот же ли это объект в памяти. equals() спрашивает про содержимое: равны ли объекты по смыслу. Для примитивов == сравнивает значения напрямую, но для объектов он проверяет лишь ссылку — и потому два равных по значению String или Integer вполне могут оказаться разными экземплярами.
Хеширование — продолжение той же истории. Хеш-коллекции (HashMap, HashSet) сначала выбирают корзину по hashCode(), а внутри неё сверяют equals(). Из этого вырастает жёсткий контракт: равные объекты обязаны возвращать одинаковый хеш. Переопределить equals() и забыть hashCode() — значит потерять ключ: объект ляжет в одну корзину, а искаться будет в другой. Полная карта — в слоях ниже.
Карта темы
- == против equals() — ссылочное равенство против логического и почему для
Stringи обёрток всегдаequals(). - Контракт equals/hashCode — почему переопределять оба вместе и как равные объекты обязаны делить
hashCode.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Сравнивать содержимое String через == | Иногда «срабатывает» на интернированных литералах и молча ломается на new String() |
Считать, что equals() по умолчанию сравнивает содержимое | В Object он работает как == — сравнивает ссылки, пока не переопределён |
Переопределить equals(), но не hashCode() | Ключ теряется в HashMap: разные корзины при put и get |
| Считать коллизии невозможными | Разные объекты МОГУТ делить хеш — обратная часть контракта не гарантируется |
Класть изменяемые поля в hashCode() | После изменения поля объект «переезжает» в другую корзину и становится ненаходимым |
Сравнивать обёртки Integer через == | Истинно лишь в кеше −128..127; за его границей — false |
Пропустить проверку null/типа в equals() | NullPointerException или ClassCastException вместо false |
Значение для собеседований
Тему спрашивают почти на каждом junior-интервью, но проверяют не заучивание, а модель: понимаете ли вы разницу между идентичностью и содержимым и то, как эта разница управляет хеш-коллекциями.
Что обычно проверяют:
- Чем
==отличается отequals()для объектов и для примитивов. - Что делает
Object.equals()по умолчанию и зачем его переопределяют. - Обе части контракта
equals/hashCodeи почему методы переопределяют вместе. - Почему ключ теряется в
HashMap, если переопределён толькоequals(). - Ловушку
==наIntegerс кешем−128..127.
Типичный неверный ответ: «== и equals() — это одно и то же, просто разный синтаксис». Это открывает разговор о том, что для объектов == сравнивает ссылки, а equals() — содержимое, и именно поэтому new String("a") == new String("a") даёт false, а .equals() — true.