JavaRush /Курсы /JAVA 25 SELF /equals, hashCode, toString: автогенерация

equals, hashCode, toString: автогенерация

JAVA 25 SELF
22 уровень , 2 лекция
Открыта

1. Автоматическая генерация equals, hashCode, toString

Зачем нужны эти методы?

Работая с объектами в Java, вы довольно быстро сталкиваетесь с одними и теми же задачами. Иногда нужно проверить, равны ли два объекта. Например, понять, есть ли он уже в коллекции вроде Set или Map. В других случаях объект используется как ключ в HashMap, и тут без специальных правил сравнения никак. А ещё почти всегда хочется напечатать объект в логе или на экране так, чтобы вывод был не просто абракадаброй вроде MyClass@7b23ec81, а что-то осмысленное.

Вот для этих случаев у каждого класса в Java есть три особых метода:

  • equals(Object o) отвечает за проверку равенства.
  • hashCode() даёт объекту числовой «отпечаток», который нужен коллекциям наподобие хэш-таблиц.
  • toString() возвращает удобное строковое представление объекта, которое здорово упрощает отладку и печать.

Почему это больно в обычных классах?

В обычных классах эти методы приходится писать вручную. И вот тут начинается скука и головная боль. Получается куча шаблонного кода, который только загромождает класс. Очень легко где-то ошибиться: забыть сравнить поле, неправильно посчитать hashCode и потом ловить загадочные баги. А если в класс добавить новое поле — придётся снова лезть во все эти методы и всё переписывать.

Пример обычного класса


public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() { return x; }
    public int y() { return y; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return x == point.x && y == point.y;
    }

    @Override
    public int hashCode() {
        return 31 * x + y;
    }

    @Override
    public String toString() {
        return "Point[x=" + x + ", y=" + y + "]";
    }
}

Выглядит знакомо? Да, и это только для двух полей! А если их двадцать?

Как это делает record

Record-класс делает всё это за вас. Просто объявите:


public record Point(int x, int y) { }

И Java САМА сгенерирует:

  • Конструктор
  • Геттеры (x(), y())
  • equals, hashCode, toString

Автоматически сгенерированные методы

  • equals сравнивает все компоненты record по значению.
  • hashCode вычисляется по всем компонентам.
  • toString возвращает строку вида Point[x=1, y=2].

Давайте посмотрим вживую!


public record Point(int x, int y) {}

public class Demo {
    public static void main(String[] args) {
        Point p1 = new Point(1, 2);
        Point p2 = new Point(1, 2);

        System.out.println(p1.equals(p2)); // true
        System.out.println(p1.hashCode() == p2.hashCode()); // true
        System.out.println(p1); // Point[x=1, y=2]
    }
}

Вывод:

true
true
Point[x=1, y=2]

Всё работает, как и ожидалось, без единой лишней строчки кода!

2. Почему это важно: коллекции, отладка и безопасность

Корректная работа в коллекциях

Представьте, что вы используете объекты как ключи в HashMap или элементы в HashSet. Если equals и hashCode реализованы неправильно — коллекции будут вести себя странно: не найдут элемент, который вы только что добавили, или, наоборот, посчитают два разных объекта одинаковыми.

С record-классами вы можете быть уверены: сравнение и хэш всегда учитывают все компоненты record (в том порядке, в котором они объявлены).

Пример: использование record как ключа


import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) {
        record Point(int x, int y) {}

        Map<Point, String> map = new HashMap<>();
        Point p1 = new Point(3, 4);
        map.put(p1, "Hello!");

        Point p2 = new Point(3, 4);
        System.out.println(map.get(p2)); // "Hello!" — работает!
    }
}

Обратите внимание: p1 и p2 — разные объекты (разные ссылки), но содержат одинаковые значения полей, поэтому считаются равными. А подробнее о Map и HashMap вы узнаете в уровне 26 :P

Удобство отладки и логирования

Вместо унылого Point@1a2b3c4d (как это бывает по умолчанию у обычных классов), record-класс печатается красиво и информативно:

Point[x=3, y=4]

Это здорово экономит время при отладке и логировании.

3. Как работает equals, hashCode, toString внутри record

Метод equals

Record-класс реализует equals так, что два объекта считаются равными, если:

  • Они одного типа (одного класса record)
  • Все их компоненты равны (== для примитивов, equals() для объектов)

Пример сравнения


Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
Point p3 = new Point(1, 3);

System.out.println(p1.equals(p2)); // true
System.out.println(p1.equals(p3)); // false

Метод hashCode

Хэш-код вычисляется по всем компонентам record, обычно с помощью стандартного метода Objects.hash(...).


System.out.println(p1.hashCode()); // Например, 994
System.out.println(p2.hashCode()); // Тоже 994
System.out.println(p3.hashCode()); // Другое число

Метод toString

Строковое представление всегда в формате:

ClassName[field1=value1, field2=value2, ...]

System.out.println(p1); // Point[x=1, y=2]

4. Переопределение equals, hashCode, toString: когда и как?

Иногда (редко, но бывает) нужно изменить стандартное поведение этих методов. Например, вы хотите, чтобы toString возвращал строку в другом формате, или чтобы сравнение происходило только по части полей.

Внимание: если вы переопределяете equals/hashCode, делайте это очень осознанно! Нарушение их «контракта» может привести к багам, которые тяжело отлавливать.

Как переопределить метод

Просто объявите свой метод внутри тела record-класса:


public record Point(int x, int y) {
    @Override
    public String toString() {
        return "(" + x + "; " + y + ")";
    }
}

Point p = new Point(3, 5);
System.out.println(p); // (3; 5)

Можно ли переопределить equals/hashCode?

Да, но крайне не рекомендуется, если вы не уверены, что делаете. Например, если вы хотите, чтобы сравнение шло только по полю x (что уже странно):


public record Point(int x, int y) {
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Point other)) return false;
        return x == other.x;
    }

    @Override
    public int hashCode() {
        return Integer.hashCode(x);
    }
}

Point p1 = new Point(1, 2);
Point p2 = new Point(1, 999);

System.out.println(p1.equals(p2)); // true (!)

Но будьте осторожны: если вы переопределяете equals, всегда переопределяйте и hashCode — иначе коллекции будут работать некорректно.

Best practice

  • Если не знаете точно, зачем переопределять — не переопределяйте!
  • Для toString — можно смело делать свой формат, если хочется.
  • Для equals/hashCode — только если есть веская причина и вы понимаете последствия.

5. Практика: сравнение объектов и использование record в коллекциях

Пример: сравнение двух record-объектов


public record User(String name, int age) {}

public class Demo {
    public static void main(String[] args) {
        User u1 = new User("Alice", 20);
        User u2 = new User("Alice", 20);
        User u3 = new User("Bob", 25);

        System.out.println(u1.equals(u2)); // true
        System.out.println(u1.equals(u3)); // false

        System.out.println(u1.hashCode() == u2.hashCode()); // true
        System.out.println(u1); // User[name=Alice, age=20]
    }
}

Пример: использование record как ключа в HashMap

Давайте представим, что у нас есть приложение, где мы храним количество посещений пользователей по их имени и возрасту (ну мало ли, вдруг в клубе есть два «Иван 20 лет»).


import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) {
        record User(String name, int age) {}

        Map<User, Integer> visits = new HashMap<>();
        User ivan20 = new User("Ivan", 20);
        User ivan22 = new User("Ivan", 22);

        visits.put(ivan20, 5);
        visits.put(ivan22, 2);

        // Проверим, что поиск по значению работает корректно
        System.out.println(visits.get(new User("Ivan", 20))); // 5
        System.out.println(visits.get(new User("Ivan", 22))); // 2
    }
}

Если бы equals и hashCode не были реализованы правильно, поиск бы не сработал. А подробнее о Map и HashMap вы узнаете из лекций 26-го уровня :P

6. Типичные ошибки при работе с equals, hashCode, toString в record-классах

Ошибка №1: Ожидание, что поля можно менять после создания.
Поля record всегда final, и сравнение идёт по их значениям, которые заданы в конструкторе. Если вдруг вы каким-то хитрым способом «меняете» внутреннее состояние (например, через мутируемый объект внутри поля), сравнение и хэш могут стать некорректными.

Ошибка №2: Переопределили equals, но забыли про hashCode.
Если вы переопределяете один из этих методов — всегда переопределяйте второй! Иначе коллекции (HashSet, HashMap) будут вести себя непредсказуемо.

Ошибка №3: Ожидание, что toString будет в каком-то другом формате.
Если вам нужен особый формат строки — просто переопределите toString. По умолчанию формат всегда ClassName[field1=value1, field2=value2].

Ошибка №4: Использование record для сложных классов с mutable-полями.
Поля record должны быть неизменяемыми. Если в качестве поля вы используете, например, ArrayList, и кто-то меняет его содержимое — сравнение и хэш-код могут «сломаться». Для record лучше использовать только неизменяемые типы.

Ошибка №5: Использование record для классов с поведением, не являющимся value-object.
Record — это не «маленький класс с коротким синтаксисом». Это именно value-object, предназначенный для хранения набора значений. Если у вас сложная логика, mutable-состояние или необходимость в наследовании — используйте обычный класс.

1
Задача
JAVA 25 SELF, 22 уровень, 2 лекция
Недоступна
Детали книги в отчете библиотеки 📚
Детали книги в отчете библиотеки 📚
1
Задача
JAVA 25 SELF, 22 уровень, 2 лекция
Недоступна
Идентификация местоположения на карте 📍
Идентификация местоположения на карте 📍
1
Задача
JAVA 25 SELF, 22 уровень, 2 лекция
Недоступна
Красивое представление профиля пользователя 🧑‍💻
Красивое представление профиля пользователя 🧑‍💻
1
Задача
JAVA 25 SELF, 22 уровень, 2 лекция
Недоступна
Идентификация товаров по названию в магазине 🏷️
Идентификация товаров по названию в магазине 🏷️
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Riga Уровень 25
7 декабря 2025
а это норм, что идея тебе сама предлагает за тебя написать переопределенные методы equals и hashCode? а тебе лишь часть кода оттуда нужно удалить и все.
Xaxatumba Уровень 38
13 ноября 2025
Лекция супер. Если сравнивать со старым курсом. Логика проста не знаешь не лезь. Класс. Так на собеседовании и отвечать IDEA всё сама правильно сделает нечего туда лезть 😂.