← Назад к вопросам

Для чего нужны records в Java?

2.2 Middle🔥 61 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Для чего нужны records в Java?

Records — это новая особенность Java (введена в Java 14 как preview, финализирована в Java 16), которая позволяет создавать неизменяемые (immutable) классы данных с минимумом кода. Records убирают большую часть boilerplate кода, который раньше нужно было писать для простых классов.

Основное назначение

Records используются для:

  1. Передачи данных (Data Transfer Objects, DTOs)
  2. Создания неизменяемых объектов (immutable objects)
  3. Снижения boilerplate кода (нет нужды писать getters, equals, hashCode, toString)
  4. Шаблонов значений (value objects)

Сравнение: Classes vs Records

Было раньше (обычный класс)

class UserDTO {
    private final String name;
    private final String email;
    private final int age;
    
    public UserDTO(String name, String email, int age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }
    
    public String getName() { return name; }
    public String getEmail() { return email; }
    public int getAge() { return age; }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        UserDTO userDTO = (UserDTO) o;
        return age == userDTO.age &&
               Objects.equals(name, userDTO.name) &&
               Objects.equals(email, userDTO.email);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, email, age);
    }
    
    @Override
    public String toString() {
        return "UserDTO{" +
                "name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                '}';
    }
}

Много кода для простого хранения данных!

Теперь с Records (Java 16+)

record UserDTO(String name, String email, int age) {}

Одна строка! Это эквивалентно 50 строкам кода выше.

Что автоматически генерирует Record?

Когда вы пишете:

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

Java автоматически создаёт:

  1. Приватные final поля для каждого компонента (name, email, age)
  2. Компактный конструктор (принимает все компоненты)
  3. Getters с названиями компонентов (не getName(), а name())
  4. equals() — сравнивает все компоненты
  5. hashCode() — на основе всех компонентов
  6. toString() — красивое представление

Пример: API Response DTO

// Вместо большого класса
record UserResponse(UUID id, String name, String email, LocalDateTime createdAt) {}

// Uso:
public ResponseEntity<UserResponse> getUser(@PathVariable UUID id) {
    User user = userService.findById(id);
    return ResponseEntity.ok(
        new UserResponse(user.getId(), user.getName(), user.getEmail(), user.getCreatedAt())
    );
}

// Клиент получит JSON:
// {
//   "id": "123e4567-e89b-12d3-a456-426614174000",
//   "name": "John Doe",
//   "email": "john@example.com",
//   "createdAt": "2026-03-28T10:30:00Z"
// }

Accessors: методы доступа

Geters в records называются accessors и используют имена компонентов:

record Point(int x, int y) {}

// Uso:
Point p = new Point(10, 20);
int x = p.x();   // НЕ p.getX()!
int y = p.y();   // НЕ p.getY()!

Методы в Records

Вы можете добавить свои методы в record:

record Rectangle(int width, int height) {
    // Свой метод
    public int area() {
        return width * height;
    }
    
    // Свой статический метод
    public static Rectangle square(int side) {
        return new Rectangle(side, side);
    }
}

// Uso:
Rectangle rect = Rectangle.square(10);
int area = rect.area();  // 100

Компактный конструктор (Compact Constructor)

Для валидации можно использовать компактный конструктор:

record User(String name, int age) {
    // Компактный конструктор (без параметров)
    public User {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be empty");
        }
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Invalid age");
        }
    }
}

// Uso:
new User("", 25);  // ❌ Выбросит исключение
new User("John", 25);  // ✅ OK

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

Records делают Lombok менее необходимым:

// ❌ Старый подход с Lombok
@Data
@AllArgsConstructor
@NoArgsConstructor
class UserDTO {
    private String name;
    private String email;
    private int age;
}

// ✅ Новый подход с Records
record UserDTO(String name, String email, int age) {}

Наследование: Records не поддерживают

Record НЕ может наследоваться от другого класса (кроме Record):

// ❌ Ошибка!
class Base {}
record Child(String name) extends Base {}  // Не компилируется

// ✅ Правильно
record Point(int x, int y) {}
record Point3D(int x, int y, int z) {}  // Отдельный record

Jackson интеграция (JSON сериализация)

Records отлично работают с Jackson для JSON:

record UserDTO(String name, String email) {}

@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        // Jackson автоматически сериализует record в JSON
        return ResponseEntity.ok(new UserDTO("John", "john@example.com"));
    }
    
    @PostMapping("/users")
    public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO dto) {
        // Jackson автоматически десериализует JSON в record
        return ResponseEntity.ok(userService.create(dto.name(), dto.email()));
    }
}

Spring Data с Records

Records отлично работают как DTOs в Spring:

// Entity (обычный класс)
@Entity
@Table(name = "users")
public class User {
    @Id
    private UUID id;
    private String name;
    private String email;
}

// DTO для API (record)
public record UserResponse(UUID id, String name, String email) {}

// Маппинг
@Service
public class UserService {
    public UserResponse mapToDTO(User user) {
        return new UserResponse(user.getId(), user.getName(), user.getEmail());
    }
}

Immutability гарантия

Records по определению неизменяемы:

record User(String name) {}

User user = new User("John");
// user.name = "Jane";  // ❌ Ошибка! Поле финальное
// user.name() возвращает String (неизменяемый)

Сравнение Records в других языках

// Kotlin data class (похоже на record, но старше)
data class User(val name: String, val email: String)
# Python dataclass (похоже на record)
from dataclasses import dataclass

@dataclass
class User:
    name: str
    email: str

Разница между record и interface

// ❌ НЕПРАВИЛЬНО
interface UserDTO {
    String name();
    String email();
}

// ✅ ПРАВИЛЬНО
record UserDTO(String name, String email) {}

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

Records удобны для test fixtures:

record UserFixture(UUID id, String name, String email) {}

@Test
public void testUserCreation() {
    UserFixture fixture = new UserFixture(
        UUID.randomUUID(),
        "John",
        "john@example.com"
    );
    
    assertEquals("John", fixture.name());
}

Pattern Matching с Records (Java 21+)

Records работают с pattern matching:

record Point(int x, int y) {}
record Rect(Point topLeft, Point bottomRight) {}

// Pattern matching
if (shape instanceof Point(int x, int y)) {
    System.out.println("Point at " + x + ", " + y);
} else if (shape instanceof Rect(Point tl, Point br)) {
    System.out.println("Rectangle from " + tl + " to " + br);
}

Ограничения Records

  1. Не может наследоваться от других классов (кроме Record)
  2. Всегда неизменяемы (нет setters)
  3. Нельзя добавить экземплярные переменные (только компоненты)
  4. Все компоненты автоматически финальные

Таблица: когда использовать Records

СлучайИспользуй
DTO / API response✅ Record
Entity JPA❌ Обычный класс
Сложная логика❌ Обычный класс
Передача данных✅ Record
Value object✅ Record
Immutable данные✅ Record
Наследование нужно❌ Обычный класс

Лучшие практики

// ✅ 1. Используй records для DTOs
public record CreateUserRequest(String email, String password) {}

// ✅ 2. Добавь валидацию в компактный конструктор
record Email(String value) {
    public Email {
        if (!value.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
}

// ✅ 3. Используй наследование через композицию
record UserDTO(UUID id, PersonData person) {}
record PersonData(String name, String email) {}

// ❌ 4. Не используй records для сложных entity
// Используй обычные классы для JPA Entity

Миграция с Lombok

// Было (Lombok)
@Data
class UserDTO {
    private String name;
    private String email;
}

// Стало (Records)
record UserDTO(String name, String email) {}

Итог: Records — это мощная особенность Java, которая убирает boilerplate код для простых классов данных. Они идеальны для DTOs, value objects и любых неизменяемых структур данных. Хотя они имеют ограничения (нет наследования, всегда неизменяемы), для правильного использования (передача данных между слоями) это именно то, что нужно. Используй records для чистоты кода и производительности разработки.