Когда нужно переопределять метод equals() в Java?

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

Ответ

Метод equals() нужно переопределять, когда логика сравнения объектов вашего класса должна основываться на равенстве их внутреннего состояния (полей), а не на равенстве ссылок в памяти (что делает реализация по умолчанию от Object).

Ключевые ситуации для переопределения equals():

  1. Использование объектов в коллекциях, основанных на хэше (HashMap, HashSet, Hashtable). Для корректной работы этих коллекций equals() должен согласовываться с hashCode().
  2. Семантическое сравнение. Например, два объекта Person с одинаковым id или email должны считаться одним и тем же человеком, даже если это разные экземпляры в памяти.
  3. Тестирование. Для удобного сравнения ожидаемого и фактического объекта в unit-тестах (хотя часто для этого используют библиотеки вроде AssertJ).

Контракт метода equals() (обязательные правила):

  • Рефлексивность: x.equals(x) всегда true.
  • Симметричность: Если x.equals(y) вернет true, то и y.equals(x) должно вернуть true.
  • Транзитивность: Если x.equals(y) и y.equals(z) вернут true, то и x.equals(z) должно вернуть true.
  • Консистентность: Многократные вызовы x.equals(y) должны consistently возвращать одно и то же значение, если поля, участвующие в сравнении, не менялись.
  • Сравнение с null: x.equals(null) всегда должно возвращать false.

Пример переопределения с использованием Objects.equals():

public class Person {
    private final Long id;
    private final String name;
    private final String email;

    @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 Objects.equals(id, person.id) &&
               Objects.equals(email, person.email); // email как уникальный идентификатор
        // Имя (name) может не участвовать в сравнении, если оно не определяет уникальность
    }

    @Override
    public int hashCode() {
        // hashCode должен строиться по тем же полям, что и equals
        return Objects.hash(id, email);
    }
}

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