Как сделать метод Contains корректным для сравнения объектов с одинаковыми значениями
Что проверяют
Знаете ли вы контракт equals и hashCode и как на него опираются коллекции. Следом часто спрашивают, почему HashSet «теряет» объект и чем record отличается от класса с ручными методами.
Как отвечать
ArrayList.contains внутри вызывает indexOf, а тот сравнивает элементы через equals. По умолчанию Object.equals сравнивает ссылки, поэтому два объекта с одинаковыми полями для списка разные. Что делать:
record: с Java 16recordгенерируетequals,hashCodeиtoStringпо всем компонентам. Для неизменяемых носителей данных это лучший вариант;- ручное переопределение: сигнатура строго
public boolean equals(Object o)с@Override. Внутри проверка ссылки, проверка типа, приведение и сравнение полей черезObjects.equals, чтобы не упасть наnull; hashCodeвместе сequals:Objects.hash(field1, field2)по тем же полям.ArrayListбез него обойдётся, ноHashSetиHashMapсначала ищут корзину по хэшу и доequalsне дойдут;getClass()илиinstanceof: первый не даёт подклассу быть равным родителю, второй это допускает, но легко ломает симметрию. Дляfinalклассов разницы нет;- Lombok:
@EqualsAndHashCodeили@Dataгенерируют методы, но в JPA-сущностях их обычно не используют.
Для сущностей Hibernate отдельная история: сравнение по всем полям ломается на ленивых прокси, поэтому равенство строят по идентификатору или натуральному ключу, аккуратно обрабатывая ещё не сохранённые объекты без id.
Сложность тоже стоит назвать: contains у списка O(n), у HashSet в среднем O(1).
Где спотыкаются
Пишут equals(MyClass other) вместо equals(Object). Это перегрузка, а не переопределение, и коллекция её не вызовет. Аннотация @Override поймала бы ошибку на компиляции.
Сравнивают поля через field.equals(other.field): на null получают NullPointerException, а для примитивного int такой код даже не скомпилируется, там нужен ==.
Кладут в HashSet объект с изменяемыми полями, меняют поле после вставки, и contains возвращает false. Хэш пересчитался, а объект остался в старой корзине.