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 лекция
Недоступна
Идентификация товаров по названию в магазине 🏷️
Идентификация товаров по названию в магазине 🏷️
Комментарии (6)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Gans Electro Уровень 42
28 июня 2026

А подробнее о Map и HashMap вы узнаете в уровне 26 :P
Не переключайтесь! Продолжение после рекламы!
Cherepoq Уровень 26
24 марта 2026
"В обычных классах эти методы приходится писать вручную. И вот тут начинается скука и головная боль. Получается куча шаблонного кода, который только загромождает класс. Очень легко где-то ошибиться: забыть сравнить поле, неправильно посчитать hashCode и потом ловить загадочные баги. А если в класс добавить новое поле — придётся снова лезть во все эти методы и всё переписывать." В IntelliJ IDEA для генерации методов hashCode() и equals() используются комбинации клавиш Alt + Insert (Windows/Linux) или ⌘Cmd + N (macOS). В Intellij IDEA для генерации кода, включая метод toString, используется комбинация клавиш Alt + Insert. Понимаю, уровня с горячими клавишами не было, но это же не значит, что их не существует. Буквально 6 кнопок + кнопки для геттеров и конструктора отделяют вот уже 5ый повторяющийся абзац неудобств обычных классов от рекорда
rwnmei Уровень 46
25 августа 2026
вообще все однотипное делается через хоткеи, все задачи решенные по крайней мере до этого уровня делаются за 6-7 нажатий 😑
Riga Уровень 5
7 декабря 2025
а это норм, что идея тебе сама предлагает за тебя написать переопределенные методы equals и hashCode? а тебе лишь часть кода оттуда нужно удалить и все.
Xaxatumba Уровень 38
13 ноября 2025
Лекция супер. Если сравнивать со старым курсом. Логика проста не знаешь не лезь. Класс. Так на собеседовании и отвечать IDEA всё сама правильно сделает нечего туда лезть 😂.
30 июня 2026
Тут подразумевается, что смысл record теряется. Раз хочется всё переопределить, то и делай обычный класс со всеми вытекающими. Это как взять БМВ и начать переделывать в Ладу