Устали писать горы шаблонного кода для простых классов данных? equalshashCodetoString, геттеры... Знакомо? Тогда встречайте Records (записи) — возможно, лучшая фича Java, которая сэкономит вам кучу времени и нервов.

❌ Проблема: Класс "вручную"

Допустим, нам нужен класс Employee (Сотрудник). Вот как он выглядит по-старинке:

public final class Employee {
    private final String name;
    private final int employeeNumber;

    public Employee(String name, int employeeNumber) {
        this.name = name;
        this.employeeNumber = employeeNumber;
    }

    public String getName() { return name; }
    public int getEmployeeNumber() { return employeeNumber; }

    @Override
    public String toString() {
        return "Employee[name=" + name + ", employeeNumber=" + employeeNumber + "]";
    }

    @Override
    public boolean equals(Object o) {
        // ... Длинная реализация с проверками ...
    }

    @Override
    public int hashCode() {
        // ... Ещё одна реализация ...
    }
}

Итого: ~50 строк кода для хранения двух полей! Большую часть этого кода можно сгенерировать, но его всё равно нужно поддерживать и читать.

✅ Решение: Record

А теперь посмотрите на магию:

public record Employee(String name, int employeeNumber) {}

Всё! Серьёзно, это полный аналог класса выше. Всего одна строка!

Что же такое Record?

Запись — это специальный вид класса, главная цель которого — быть неизменяемым (immutable) контейнером для данных.

Что компилятор генерирует за вас автоматически:

  • Приватные и финальные поля для каждого компонента (nameemployeeNumber).

  • Канонический конструктор — Employee(String name, int employeeNumber).

  • Геттеры (но без префикса get!) — name() и employeeNumber().

  • Методы toString()equals(Object o) и hashCode().

Пример использования:

// Создание записи
Employee john = new Employee("John Doe", 12345);

// Использование геттеров (без "get"!)
System.out.println(john.name()); // John Doe
System.out.println(john.employeeNumber()); // 12345

// Автоматический toString()
System.out.println(john); // Employee[name=John Doe, employeeNumber=12345]

// Работает equals и hashCode
Employee johnClone = new Employee("John Doe", 12345);
System.out.println(john.equals(johnClone)); // true

Суперсила: Кастомизация записей

Записи не совсем "закрытые коробки". Вы можете добавлять свою логику!

1. Компактный конструктор

Идеален для валидации. Параметры и присваивания не пишутся — они подразумеваются.

public record Employee(String name, int employeeNumber) {
    // Компактный конструктор
    public Employee {
        if (employeeNumber < 0) {
            throw new IllegalArgumentException("Employee number cannot be negative");
        }
        // name и employeeNumber автоматически присвоятся после этого блока
    }
}

2. Дополнительные методы

Вы можете объявлять свои методы.

public record Employee(String name, int employeeNumber) {
    // Дополнительный метод экземпляра
    public String getEmail() {
        return name.toLowerCase().replace(" ", ".") + "@company.com";
    }

    // Статический метод
    public static Employee createCEO() {
        return new Employee("The Boss", 1);
    }
}

⚠️ Важные ограничения

  1. Record неизменяемы. Нельзя добавить setter или изменить поле после создания.

  2. Не могут наследоваться. Все записи неявно являются final классами.

  3. Не могут наследовать другие классы (кроме java.lang.Record).

  4. Нельзя добавлять не-static поля экземпляра. Только те, что объявлены в заголовке.

Когда использовать Record?

  • DTO (Data Transfer Objects) — идеально для передачи данных между слоями приложения, например, из REST-контроллера.

  • Кортежи и простые контейнеры данных.

  • Ключи в Map или элементы в Set, так как у них правильно реализованы equals и hashCode.

  • Возврат нескольких значений из метода.

Вывод

Record в Java — это не просто синтаксический сахар. Это фундаментальное улучшение языка, которое делает код:

  • Короче и чище.

  • Безопаснее благодаря неизменяемости.

  • Проще для чтения и поддержки.

Если вы до сих пор не используете записи — самое время начать! Это изменит ваш подход к созданию классов-данных.