JavaRush /Курсы /JAVA 25 SELF /Record: синтаксис, преимущества

Record: синтаксис, преимущества

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

1. Проблема "классов-DTO": зачем нам record-классы?

Давайте честно: сколько раз вы писали вот такой класс?


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 getX() {
        return x;
    }

    public int getY() {
        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 Objects.hash(x, y);
    }

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

Вроде бы ничего сложного, но посчитайте строки! А теперь представьте, что у вас 10 таких классов, и в каждом по 5–6 полей. Даже IDE устает генерировать этот шаблонный код. А если вы решите добавить новое поле — придётся править конструктор, геттер, equals, hashCode, toString... Скука, рутина и источник ошибок.

Такие классы называют DTO (Data Transfer Object) или Value Object. Они просто хранят данные — и всё. Но из-за шаблонного кода поддерживать их тяжело.

Если вам кажется, что это не проблема, просто подождите, когда вам придётся менять 50 таких классов сразу. Тогда вы вспомните про record-классы с особой теплотой!

2. Введение в record: синтаксис и магия Java 16+

С Java 16 всё изменилось. Появился новый вид классов — record. Они специально созданы для случаев, когда нужно просто хранить набор данных. Синтаксис — почти как у кортежа в других языках.

Как объявить record?


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

И... всё! Вы только что создали неизменяемый класс с двумя полями, конструктором, геттерами, equals, hashCode и toString. Без лишней писанины.

Что делает Java "под капотом"?

  • Поля x и y становятся private final.
  • Геттеры создаются автоматически: int x() и int y().
  • Конструктор: public Point(int x, int y).
  • equals/hashCode: сравнивают все поля по значению.
  • toString: возвращает строку вида "Point[x=1, y=2]".

Можно сказать, что record — это «DTO на стероидах»: меньше кода, больше гарантий, меньше багов.

Неизменяемость (immutability)

Все поля record-класса автоматически final. После создания объекта изменить его нельзя — это гарантирует компилятор.

Если вы попытаетесь добавить сеттер или сделать поле не final — компилятор вас остановит. Такая забота о вашем спокойствии встречается нечасто!

3. Пример использования record-класса

Обычный класс (много кода):


public class Client {
    private final String name;
    private final int id;

    public Client(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public String getName() { return name; }
    public int getId() { return id; }

    // equals, hashCode, toString ...
}

Record-класс (одна строка!):


public record Client(String name, int id) { }

Использование:


public class Main {
    public static void main(String[] args) {
        Client client = new Client("Иван", 123);
        System.out.println(client.name()); // Иван
        System.out.println(client.id());   // 123
        System.out.println(client);        // Client[name=Иван, id=123]
    }
}

Обратите внимание:
- Методы доступа к полям называются так же, как поля: name(), id().
- Нет setName() или setId() — объект нельзя изменить после создания.

4. Преимущества record-классов: меньше кода, меньше ошибок

Меньше кода — больше счастья

Зачем писать 40 строк, если можно обойтись одной? Record-классы экономят время и нервы, особенно в больших проектах, где много DTO и value-объектов.

Неизменяемость "по контракту"

  • Record-классы всегда final и неизменяемы.
  • Объект нельзя подделать или случайно изменить.
  • Нет «странных» багов из-за изменения состояния объекта в неожиданном месте.
  • Можно безопасно использовать в многопоточных программах (если все поля тоже immutable).

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

Не нужно ручками писать методы сравнения, вычисления хэша и красивого вывода. Всё делается автоматически и правильно.


Client c1 = new Client("Анна", 42);
Client c2 = new Client("Анна", 42);

System.out.println(c1.equals(c2));                 // true
System.out.println(c1.hashCode() == c2.hashCode()); // true
System.out.println(c1);                             // Client[name=Анна, id=42]

Идеально для коллекций и ключей

Record-объекты можно использовать как ключи в HashMap, элементы в HashSet и т.д. — всё будет работать корректно, потому что equals и hashCode учитывают все поля.


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

Map<Client, String> clients = new HashMap<>();
clients.put(new Client("Анна", 42), "VIP");

System.out.println(clients.get(new Client("Анна", 42))); // VIP

Явное описание данных

Синтаксис record-класса сразу показывает, какие данные хранятся и что объект неизменяемый. Это делает код понятнее для других разработчиков (и для вас через полгода).

5. Таблица: сравнение обычного класса и record-класса

Обычный класс Record-класс
Синтаксис Много кода Одна строка
Неизменяемость Нужно явно писать Гарантирована компилятором
Автогенерация методов Нет Да (equals, hashCode, toString)
Можно добавить поля Да Только компоненты record
Наследование Можно наследовать Всегда final, наследовать нельзя
Использование в коллекциях Нужно корректно реализовать методы Работает «из коробки»

6. Развиваем учебное приложение: пример с record-классом

Допустим, в вашем учебном банковском приложении нужно хранить операции по счету: дата, сумма, тип операции (например, «пополнение» или «снятие»).

До Java 16:


public class Transaction {
    private final LocalDate date;
    private final double amount;
    private final String type;

    public Transaction(LocalDate date, double amount, String type) {
        this.date = date;
        this.amount = amount;
        this.type = type;
    }

    public LocalDate getDate() { return date; }
    public double getAmount() { return amount; }
    public String getType() { return type; }

    // equals, hashCode, toString ...
}

С Java 16 и record:


import java.time.LocalDate;

public record Transaction(LocalDate date, double amount, String type) { }

Использование:


Transaction t = new Transaction(LocalDate.now(), 100.0, "deposit");
System.out.println(t);         // Transaction[date=2024-06-01, amount=100.0, type=deposit]
System.out.println(t.amount()); // 100.0

7. Визуализация: что генерирует record

Посмотрим на «развёрнутый» record-класс (примерно, что сгенерирует компилятор):


public final class Point extends java.lang.Record {
    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) { /* сравнение по полям */ }

    @Override
    public int hashCode() { /* вычисление по полям */ }

    @Override
    public String toString() { /* красивый вывод */ }
}

Кратко: когда стоит использовать record

  • Когда нужен неизменяемый объект с набором данных.
  • Когда нужен «честный» equals/hashCode/toString без ручной писанины.
  • Когда вы делаете DTO, Value Object, пары, тройки, цвет, точку, диапазон, ключ для коллекции и т.п.

8. Типичные ошибки при работе с record-классами

Ошибка №1: попытка добавить сеттер или изменить поле после создания.
Record-класс не позволяет изменять свои поля. Если вы попробуете добавить метод вроде setX(int x), компилятор сразу скажет «нельзя». То же самое — если попытаться изменить поле напрямую.

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

Ошибка №3: использование record для mutable-логики.
Record-классы не предназначены для объектов с изменяемым состоянием. Если вам нужно что-то менять после создания — используйте обычный класс.

Ошибка №4: забыли, что record всегда final.
Record-класс нельзя наследовать и нельзя сделать его суперклассом. Попытка нарушить это ограничение приведёт к ошибке компиляции. Ключевой признак — не пытаемся «расширять» record: он задуман как законченный неизменяемый тип.

Ошибка №5: игнорирование автогенерируемых методов.
Если вы переопределяете equals, hashCode или toString, будьте осторожны — не нарушайте их контракт, иначе коллекции и сравнения будут работать некорректно.

1
Задача
JAVA 25 SELF, 22 уровень, 0 лекция
Недоступна
Создание цифровой карточки книги 📚
Создание цифровой карточки книги 📚
1
Задача
JAVA 25 SELF, 22 уровень, 0 лекция
Недоступна
Просмотр данных студента 🎓
Просмотр данных студента 🎓
1
Задача
JAVA 25 SELF, 22 уровень, 0 лекция
Недоступна
Проверка идентичности городов на карте 🗺️
Проверка идентичности городов на карте 🗺️
1
Задача
JAVA 25 SELF, 22 уровень, 0 лекция
Недоступна
Управление доступом пользователей в приложении 🔑
Управление доступом пользователей в приложении 🔑
Комментарии (4)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Xaxatumba Уровень 38
13 ноября 2025
Давайте честно: сколько раз вы писали вот такой класс? Ни разу. Вы нас только учили как кров с котами насиловать. Вот вся лекция и рассыпалась. 🤪 Сами классы DTO конечно полезны, но зачем они нужны на данном этапе обучения вообще не ясно. Хотя можно хранить котов с коровами в коллекциях которые буду на 26 уровне. А ещё главное не забыть переопределить equals, hashCode или toString. Нам ведь это тоже уже объяснили 👍.
Big198801 Уровень 29
5 ноября 2025
все 4 задачки решаются одинаково) Создал Record-класс и можно на проверку отправлять) в Солюшене все дописано за нас)
Xaxatumba Уровень 38
13 ноября 2025
В этом одна из проблем нового курса. И я так понял менять они не будут.
Andrey Уровень 1
17 сентября 2025
22