Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Java Records: современный способ создания неизменяемых объектов данных
Records (записи) — это специальный тип класса в Java, введённый в Java 14 (preview) и стабилизированный в Java 16. Они предназначены для создания неизменяемых объектов данных с минимальным количеством boilerplate кода.
Проблема, которую решают Records
Традиционно для создания простого класса данных требуется писать много стандартного кода:
// ДО Records: много boilerplate
public class Person {
private final String name;
private final int age;
private final String email;
public Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String name() {
return name;
}
public int age() {
return age;
}
public String email() {
return email;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name) &&
Objects.equals(email, person.email);
}
@Override
public int hashCode() {
return Objects.hash(name, age, email);
}
@Override
public String toString() {
return "Person{" + "name='" + name + "'" +
", age=" + age + ", email='" + email + "'" + "}";
}
}
Решение: Records
Тот же функционал в одной строке:
public record Person(String name, int age, String email) {}
Это всё! Компилятор автоматически генерирует:
- Приватные финальные поля для каждого компонента
- Канонический конструктор
- Методы доступа (accessor methods) — name(), age(), email()
- Методы equals(), hashCode() и toString()
Использование Records
// Создание экземпляра
Person person = new Person("John Doe", 30, "john@example.com");
// Использование accessor методов
System.out.println(person.name()); // John Doe
System.out.println(person.age()); // 30
System.out.println(person.email()); // john@example.com
// toString генерируется автоматически
System.out.println(person);
// Person[name=John Doe, age=30, email=john@example.com]
// equals и hashCode работают корректно
Person person2 = new Person("John Doe", 30, "john@example.com");
System.out.println(person.equals(person2)); // true
Расширенные возможности Records
1. Compact конструктор (validation)
public record Person(String name, int age, String email) {
// Compact конструктор — выполняется ДО установки полей
public Person {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
}
}
// Использование с validation
try {
Person person = new Person("", 30, "john@example.com");
} catch (IllegalArgumentException e) {
System.out.println("Validation failed: " + e.getMessage());
}
2. Дополнительные методы
public record Point(int x, int y) {
// Дополнительный метод
public int distance() {
return Math.abs(x) + Math.abs(y);
}
// Переопределение toString
@Override
public String toString() {
return "Point(" + x + ", " + y + ")";
}
}
Point p = new Point(3, 4);
System.out.println(p.distance()); // 7
3. Наследование от record Records не могут наследоваться от других классов (кроме java.lang.Record), но могут реализовывать интерфейсы:
public interface Serializable {}
public record UserData(String name, String email) implements Serializable {}
// В Java 21+ можно создавать sealed records
public sealed record Dog(String name) permits GermanShepherd, Poodle {}
public record GermanShepherd(String name) extends Dog {}
public record Poodle(String name) extends Dog {}
Records vs Lombok
До Records многие использовали Lombok:
// Lombok подход
@Data
@RequiredArgsConstructor
public class PersonLombok {
private final String name;
private final int age;
private final String email;
}
// Records подход (встроен в язык, без зависимостей)
public record Person(String name, int age, String email) {}
Преимущества Records:
- Нет внешних зависимостей
- IDE и инструменты имеют встроенную поддержку
- Явное намерение создать immutable data object
- Лучше для serialization и pattern matching
Практические примеры
1. API Response DTO
public record UserResponse(Long id, String name, String email, LocalDateTime createdAt) {}
// Использование в REST контроллере
@GetMapping("/{id}")
public UserResponse getUser(@PathVariable Long id) {
User user = userService.findById(id);
return new UserResponse(user.getId(), user.getName(), user.getEmail(), user.getCreatedAt());
}
2. Pattern matching с Records (Java 16+)
public sealed interface Animal {}
public record Dog(String breed) implements Animal {}
public record Cat(String color) implements Animal {}
public record Bird(double wingSpan) implements Animal {}
public String describeAnimal(Animal animal) {
return switch (animal) {
case Dog(String breed) -> "Dog with breed: " + breed;
case Cat(String color) -> "Cat with color: " + color;
case Bird(double wingSpan) -> "Bird with wingspan: " + wingSpan;
};
}
3. Использование в функциональном программировании
public record Result<T>(T value, Optional<Exception> error) {}
public Result<Integer> divide(int a, int b) {
try {
return new Result<>(a / b, Optional.empty());
} catch (ArithmeticException e) {
return new Result<>(null, Optional.of(e));
}
}
Когда использовать Records
✅ Используй Records когда:
- Нужен неизменяемый класс для данных
- Хочешь минимизировать boilerplate
- Работаешь с Java 16+
- Создаёшь DTOs, request/response объекты, data classes
❌ Избегай Records когда:
- Нужна mutability (изменяемость)
- Требуется наследование (кроме интерфейсов)
- Нужны сложные инварианты, требующие много логики
- Нужна обратная совместимость с Java 15 и ранее
Итоги
Records — это эволюция языка Java, которая делает его более современным и выразительным. Они значительно уменьшают количество стандартного кода при создании data classes, что улучшает читаемость и поддерживаемость кода.