У чому основні відмінності 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(); // ✅ НЕ user.getName()
user.age(); // ✅ НЕ 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 фінальними]]