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

Ответ

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

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

  1. Рефлексивность: x.equals(x) всегда true.
  2. Симметричность: если x.equals(y) == true, то и y.equals(x) == true.
  3. Транзитивность: если x.equals(y) и y.equals(z), то x.equals(z).
  4. Согласованность: многократные вызовы x.equals(y) должны стабильно возвращать одно и то же значение, если данные объектов не менялись.
  5. Сравнение с null: x.equals(null) всегда false.

Правила переопределения:

  • Всегда переопределяйте hashCode(), если переопределяете equals(). Объекты, которые равны по equals(), должны возвращать одинаковый хэш-код.
  • Используйте instanceof или проверку класса для сравнения типов.
  • Для сравнения полей-примитивов используйте ==, для объектов — Objects.equals() (корректно обрабатывает null).

Пример корректного переопределения:

public class Person {
    private String name;
    private int age;

    @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;
        // 4. Сравнение значимых полей
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        // Генерация хэш-кода на основе тех же полей
        return Objects.hash(name, age);
    }
}

Использование Objects.equals(): Этот метод безопасен для null и эквивалентен конструкции (a == b) || (a != null && a.equals(b)).

Ответ 18+ 🔞

А, ну это же про этот ваш священный грааль — метод equals, блядь! Ну что, разжую, как для дебила, но с технической точностью, ёпта.

Смотри, есть у тебя два объекта. Ты на них смотришь и думаешь: «О, да это же одно и то же, сука!». А Java-то тупая, она нихуя не понимает. Для неё это два разных куска памяти, два разных адреса. И оператор == тебе так и скажет: «Не, мудила, это разные ссылки, нихуя не равны». А ты ему: «Да посмотри на содержимое, ебанашка! Имя одинаковое, возраст одинаковый!». Вот для этого и нужен equals — чтобы сравнивать не адреса в памяти, а то, что внутри, по смыслу.

Но тут, сука, не всё так просто. Нельзя просто взять и написать equals как попало. Есть там, блядь, целый контракт, правила, которые нарушать — себя не уважать.

Контракт, блядь, или «правила хорошего тона для equals»:

  1. Рефлексивность. Объект должен быть равен самому себе. x.equals(x) — всегда true. Если это не так, ты просто конченый идиот.
  2. Симметричность. Если x равен y, то и y должен быть равен x. Не может быть такого, что Вася считает Петю своим братом, а Петя Ваську — хуем с горы.
  3. Транзитивность. Если x равен y, а y равен z, то x обязан быть равен z. Логика, блядь, а не матан какой-то.
  4. Согласованность. Сколько раз ни вызывай equals — результат должен быть один и тот же, если данные не менялись. Не может объект сегодня быть равным, а завтра — нет, это уже шизофрения.
  5. Сравнение с null. Любой объект, даже самый ебаный, при сравнении с null должен возвращать false. x.equals(null) -> false. Всё.

А теперь, внимание, ебаный насос! Главное правило: Если переопределил equalsобязательно переопредели и hashCode(), блядь! Иначе все твои коллекции вроде HashMap или HashSet просто сойдут с ума. Они будут искать объекты не там, и ты будешь месяц дебажить эту хуйню. Объекты, которые equals говорит, что равны, обязаны иметь одинаковый хэш-код. Запомни это, как «Отче наш».

Как это делать, не наступая на грабли:

  • Сравнивай себя с собой через == — это быстрая проверка.
  • Проверяй, не null ли тебе подсунули и того же ли класса объект. instanceof или getClass() — выбирай по ситуации, но обычно getClass() строже и надёжнее.
  • Приводи тип, сука, аккуратно.
  • Сравнивай поля. Примитивы — через ==. Объекты — через Objects.equals(). Это гениальная штука, она сама разберётся с null, не надо городить велосипеды вроде if (a != null && a.equals(b)).

Смотри, как это выглядит в коде, без всякой понтовой лапши:

public class Person {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        // 1. Быстрая проверка: а не я ли это?
        if (this == o) return true;
        // 2. Он null? Или он вообще с другой планеты (другого класса)?
        if (o == null || getClass() != o.getClass()) return false;
        // 3. Ладно, приводим тип. Теперь o — это точно Person.
        Person person = (Person) o;
        // 4. И сравниваем всё, что важно. Возраст как число, имя через умный Objects.equals.
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        // Хэш-код на основе ТЕХ ЖЕ полей, что и в equals! Без этого — пиздец.
        return Objects.hash(name, age);
    }
}

Вот и вся магия, блядь. Objects.equals() — это твой лучший друг. Он делает ровно то, что ты 100 раз писал вручную: (a == b) || (a != null && a.equals(b)). Не изобретай велосипед, используй готовое, и будет тебе счастье.