Какие контракты (свойства) должен соблюдать метод `equals()` в Java?

«Какие контракты (свойства) должен соблюдать метод `equals()` в Java?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Переопределяя equals(), необходимо обеспечить выполнение пяти ключевых свойств (контрактов):

  1. Рефлексивность (Reflexive): Объект должен быть равен самому себе.
    • x.equals(x) всегда возвращает true.
  2. Симметричность (Symmetric): Если x равен y, то и y должен быть равен x.
    • Если x.equals(y) возвращает true, то y.equals(x) также должно возвращать true.
  3. Транзитивность (Transitive): Если x равен y, а y равен z, то x должен быть равен z.
    • Если x.equals(y) и y.equals(z) возвращают true, то x.equals(z) также должно возвращать true.
  4. Консистентность (Consistent): Многократные вызовы equals() для неизмененных объектов должны возвращать одинаковый результат.
    • Результат не должен зависеть от времени вызова или случайных факторов.
  5. Сравнение с null: Любой объект не должен быть равен null.
    • x.equals(null) всегда возвращает false.

Важное правило: Если переопределяется equals(), обязательно должен быть переопределен и hashCode(), чтобы выполнялся его контракт: равные объекты должны иметь равные хэш-коды.

Пример корректной реализации для класса Person:

@Override
public boolean equals(Object o) {
    // 1. Проверка на рефлексивность и идентичность ссылок
    if (this == o) return true;
    // 2. Проверка на null и совпадение класса
    if (o == null || getClass() != o.getClass()) return false;
    // 3. Сравнение значимых полей
    Person person = (Person) o;
    return age == person.age && Objects.equals(name, person.name);
}

@Override
public int hashCode() {
    return Objects.hash(name, age); // Согласованность с equals
}