В чём основные отличия Record от обычного класса
// java.lang.Record — специальный класс, который нельзя использовать напрямую через extends.
🟢 Junior Level
Record — это специальный тип класса в Java, предназначенный для хранения данных. Главное отличие: Record создан быть простым контейнером данных, а обычный класс — универсальным инструментом.
Основные отличия:
| Record | Обычный класс |
| ———————————– | ———————– |
| Все поля private final | Поля любые |
| Нельзя изменить поля после создания | Можно менять поля |
| Наследуется от java.lang.Record | Наследуется от Object |
// java.lang.Record — специальный класс, который нельзя использовать напрямую через extends.
| implicit final — нельзя расширять | Можно наследовать |
| Автогенерация equals, hashCode, toString | Нужно писать вручную или через IDE |
Пример:
// Record — одна строка
public record Point(int x, int y) {}
// Обычный класс — 30+ строк
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; }
// ещё equals, hashCode, toString...
}
🟡 Middle Level
Как это работает
Record — это ограниченная форма класса, где компилятор берёт на себя boilerplate:
1. Конструктор:
// Record — канонический конструктор автогенерируется
public record User(String name, int age) {}
// Можно добавить свой конструктор (должен вызывать канонический)
public record User(String name, int age) {
public User(String name) {
this(name, 0); // делегирование
}
}
2. Геттеры:
// Record — без приставки get!
User user = new User("John", 25);
user.name(); // ✅ NOT user.getName()
user.age(); // ✅ NOT user.getAge()
3. Наследование:
// ❌ Record нельзя расширить
public class Bad extends Point {} // compilation error
// ❌ Record не может ничего расширять (кроме java.lang.Record)
public record Bad extends Object {} // compilation error
// ✅ Record может имплементировать интерфейсы
public record User(String name) implements Serializable {}
Практическое применение
Когда использовать Record:
- DTO для REST API
- Ключи в HashMap/HashSet
- Возврат нескольких значений из метода
- Value objects в DDD
Когда обычный класс:
- Нужно mutable состояние
- Нужны дополнительные методы/поля
- Нужно наследование
- Нужен builder pattern с mutable полями
Типичные ошибки
- Попытка сделать mutable поля: ```java public record BadRecord() { // ❌ Не static поле — ошибка компиляции public int mutableField = 0; }
// ✅ Только static public record GoodRecord() { public static int counter = 0; }
2. **Ожидание get/set методов:**
```java
public record User(String name) {}
User u = new User("John");
// ❌ u.getName() — такого нет!
// ✅ u.name() — правильный вызов
🔴 Senior Level
Internal Implementation
Class file structure:
// Record — это final class с ACC_RECORD флагом
ClassFile {
access_flags: ACC_FINAL | ACC_RECORD
super_class: java/lang/Record
fields: все private final
methods: канонический конструктор + аксессоры + equals/hashCode/toString
}
Отличия на уровне JVM:
- Record имеет специальный атрибут
RecordComponentsв constant pool - JVM знает о канонических компонентах (имя, тип, аннотации)
- Reflection API:
Class.getRecordComponents()возвращаетRecordComponent[]
Архитектурные Trade-offs
Record:
- ✅ Минимум boilerplate
- ✅ Гарантированная иммутабельность
- ✅ Оптимизация под value types (Project Valhalla)
- ❌ Нет гибкости
- ❌ Нельзя использовать с JPA entities (нужен no-arg конструктор)
Обычный класс:
- ✅ Полный контроль
- ✅ JPA совместимость
- ✅ Builder, mutable state
- ❌ Больше кода
- ❌ Ручная поддержка equals/hashCode
Edge Cases
1. Компактный конструктор для валидации:
public record Email(String value) {
public Email {
if (!value.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
}
2. Аннотации на компонентах:
public record User(
@JsonProperty("user_name") String name,
@Min(18) int age
) {}
// Аннотация применяется к полю, параметру конструктора И аксессору
3. Сериализация:
// Record использует специальный механизм десериализации
// через канонический конструктор (JEP 445, Java 21)
// вместо readObject/writeObject
Производительность
Операция | Record | Обычный класс (final поля)
-------------------|--------|---------------------------
Создание объекта | 8 ns | 8 ns
equals() | 15 ns | 15 ns (ручная реализация)
hashCode() | 12 ns | 12 ns
Разница минимальна — Record не уступает
// Примерные значения. Зависят от JVM/hardware.
Production Experience
// DTO в Spring Boot 3.x — Records стали стандартом
public record CreateUserRequest(
@NotBlank String name,
@Email String email,
@Min(18) int age
) {}
// JPA entities — пока нельзя использовать Records
// (нужен mutable state, no-arg constructor, lazy loading proxies)
@Entity
public class User { // обычный класс
@Id private Long id;
private String name;
}
Best Practices
// ✅ Record для immutable DTO
public record OrderDto(String id, Instant createdAt, BigDecimal amount) {}
// ✅ Record для value objects
public record Money(BigDecimal amount, Currency currency) {
public Money {
Objects.requireNonNull(amount);
Objects.requireNonNull(currency);
}
}
// ❌ Record для JPA entity
// ❌ Record для mutable коллекций без защиты
public record BadRecord(int[] values) {} // массив mutable!
🎯 Шпаргалка для интервью
Обязательно знать:
- Record — ограниченный вид класса: только final поля, нельзя наследовать, нельзя наследовать самому
- Record наследуется от
java.lang.Record, обычный класс — отObject - Автогенерация
equals,hashCode,toString— Record vs ручной код в обычном классе - Record immutable по дизайну; обычный класс может быть mutable или immutable
- Record нельзя использовать с JPA (нужен no-arg конструктор, mutable state)
- Record геттеры без
get:user.name()вместоuser.getName()
Частые уточняющие вопросы:
- Когда выбирать Record, а когда обычный класс? — Record для immutable DTO/value objects, обычный класс для JPA, mutable state, builder
- Можно ли добавить дополнительные методы в Record? — Да, любые static и instance методы, но не instance поля
- Record быстрее обычного класса? — Нет, производительность идентична, разница < 5%
- Можно ли переопределить equals/hashCode в Record? — Да, но это редко нужно
Красные флаги (НЕ говорить):
- ❌ “Record — это подкласс обычного класса” — Record наследуется от
java.lang.Record, специального класса - ❌ “Record поддерживает наследование” — Record implicit final
- ❌ “Record использует get/set” — аксессоры без get:
name()неgetName() - ❌ “Record подходит для JPA” — JPA требует no-arg конструктор и mutable поля
Связанные темы:
- [[1. Что такое Record в Java и с какой версии они доступны]]
- [[3. Можно ли наследоваться от Record или наследовать Record от другого класса]]
- [[5. Какие методы автоматически генерируются для Record]]
- [[9. Являются ли поля Record финальными]]