Равенство и хеширование
Контракт equals/hashCode, == и equals(), типичные ошибки равенства и хеширования.
3 вопросов
JuniorТеорияОчень частоВ чём разница между операторами == и equals() в Java?
В чём разница между операторами == и equals() в Java?
Для объектов == сравнивает ссылки (идентичность) — указывают ли обе на один экземпляр — а equals() сравнивает логическое содержимое и переопределяем. Для примитивов == сравнивает значения напрямую. Поэтому для String и обёрток вроде Integer всегда используйте equals(), так как == проверяет лишь идентичность объектов, и два равных по значению объекта могут быть разными экземплярами.
Типичные ошибки
- ✗Сравнивать содержимое
Stringчерез==и обманываться, когда интернированные литералы случайно совпадают - ✗Считать, что
equals()сравнивает идентичность, а не переопределяемое логическое содержимое - ✗Забывать, что
==для примитивов сравнивает значения, а не ссылки
Уточняющие вопросы
- →Почему
==для двух равных литераловStringможет вернуть true, но не сработает сnew String()? - →Что делает
Object.equals()по умолчанию, если класс его не переопределяет?
MiddleТеорияЧастоКаков контракт между equals() и hashCode() в Java?
Каков контракт между equals() и hashCode() в Java?
Контракт: если два объекта равны по equals(), они ОБЯЗАНЫ вернуть одинаковый hashCode(). Обратное не требуется — неравные объекты МОГУТ иметь одинаковый хеш (коллизия). Поэтому переопределяя equals(), нужно согласованно переопределить hashCode() по тем же полям. Нарушите это — и хеш-коллекции вроде HashMap или HashSet сломаются: объект попадает в одну корзину, а ищется в другой, и поиск молча промахивается.
Типичные ошибки
- ✗Переопределить
equals(), но оставить унаследованныйhashCode(), ломая поиск вHashMap - ✗Считать, что равные хеш-коды означают равные объекты, полагая коллизии невозможными
- ✗Использовать разные поля в
equals()иhashCode(), нарушая согласованность
Уточняющие вопросы
- →Почему ключ теряется в
HashMap, если переопределён толькоequals()? - →Корректно ли возвращать константу из
hashCode(), и чего это стоит?
SeniorТеорияИногдаКаковы типичные ошибки равенства и хеширования в Java?
Каковы типичные ошибки равенства и хеширования в Java?
Главные ловушки: переопределить equals(), но не hashCode() — и ключ пропадает из HashMap после вставки; включить изменяемые поля в hashCode() — объект меняет корзину после сохранения и больше не находится; пропустить проверки на null и тип внутри equals() — это даёт неверный результат или NullPointerException; и применять == к автоупакованным Integer, где кеш −128..127 делает его то истинным, то ложным, маскируя баги в тестах.
Типичные ошибки
- ✗Класть изменяемые поля в
hashCode(), из-за чего сохранённый ключ переезжает и поиск падает - ✗Сравнивать упакованные
Integerчерез==и проходить тесты лишь в кеше −128..127 - ✗Опускать проверку на null или тип в
equals(), получая исключение или неверный результат
Уточняющие вопросы
- →Почему изменение поля из
hashCode()делает элементHashSetнедостижимым? - →Как кеширование
Integer.valueOfделает==ненадёжным по диапазону int?