Ответ
Контракт метода equals(Object obj), определенный в классе Object, состоит из пяти обязательных свойств. Их нарушение может привести к непредсказуемому поведению коллекций (HashMap, HashSet).
Контракт equals():
- Рефлексивность (Reflexive):
x.equals(x)всегда должно возвращатьtrue. - Симметричность (Symmetric): Если
x.equals(y)возвращаетtrue, тоy.equals(x)также должно возвращатьtrue. - Транзитивность (Transitive): Если
x.equals(y)иy.equals(z)возвращаютtrue, тоx.equals(z)также должно возвращатьtrue. - Консистентность (Consistent): Многократные вызовы
x.equals(y)должны стабильно возвращать одно и то же значение, при условии, что поля, используемые в сравнении, не изменялись. - Сравнение с null:
x.equals(null)всегда должно возвращатьfalse.
Правила переопределения:
- Всегда переопределяйте
hashCode()вместе сequals(). Это требование контрактаObject: равные объекты должны иметь равные хэш-коды. - Используйте
instanceofдля проверки типа, если класс неfinalи вы допускаете сравнение с подклассами. - Используйте
==для сравнения примитивных полей иObjects.equals()для сравнения ссылочных полей (корректно обрабатываетnull).
Шаблон корректной реализации:
import java.util.Objects;
public final class Person { // final класс
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
// 1. Проверка ссылки на себя
if (this == o) return true;
// 2. Проверка на null и совместимость типов (instanceof для не-final классов)
if (o == null || getClass() != o.getClass()) return false;
// 3. Приведение типа
Person person = (Person) o;
// 4. Сравнение значимых полей
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
// Генерация хэш-кода на основе тех же полей, что и в equals()
return Objects.hash(name, age);
}
}
Почему getClass() вместо instanceof в примере? Для final-класса Person использование getClass() гарантирует строгое равенство: объект Person будет равен только другому объекту Person, но не объекту его подкласса (которого не может быть). Если класс не final и логика равенства должна работать с наследниками, используйте instanceof и сравнивайте все значимые поля, доступные в базовом классе.