Що таке компактний конструктор (compact constructor) в Record
Він не містить сигнатури — компілятор сам розуміє, що це компактний конструктор.
🟢 Junior Level
Компактний конструктор — це спеціальна форма конструктора в Record, яка використовується тільки для валідації та нормалізації даних.
Він не містить сигнатури — компілятор сам розуміє, що це компактний конструктор.
public record User(String name, int age) {
// Компактний конструктор — тільки тіло, без сигнатури
public User {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
}
Відмінності від звичайного конструктора:
// Звичайний конструктор
public User(String name, int age) {
this.name = name;
this.age = age;
}
// Компактний конструктор (тільки валідація)
public User {
if (age < 0) {
throw new IllegalArgumentException();
}
}
🟡 Middle Level
Як це працює
Компактний конструктор вбудовується в канонічний конструктор автоматично:
public record Email(String value) {
public Email {
// Цей код виконається ПЕРЕД присвоюванням полів
if (value == null || !value.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
}
// Компілятор генерує:
public Email(String value) {
// Код з компактного конструктора
if (value == null || !value.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
// Потім присвоювання
this.value = value;
}
Важливо: Параметри в компактному конструкторі не фінальні — можна змінити значення перед присвоюванням!
public record Email(String value) {
public Email {
value = value.toLowerCase().trim(); // ✅ нормалізація
}
}
Типові помилки
- Спроба присвоїти
this.value:public record User(String name) { public User { // ❌ Не можна використовувати this.value this.name = name.toUpperCase(); // compilation error // ✅ Використовуйте просто name name = name.toUpperCase(); // OK — це параметр, не поле } } - Спроба додати поля:
public record User() { public User { // ❌ Не можна додати поле int extra = 0; // compilation error } }
Практичне застосування
1. Валідація:
public record Money(BigDecimal amount) {
public Money {
Objects.requireNonNull(amount, "Amount cannot be null");
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Negative amount");
}
}
}
2. Нормалізація:
public record PhoneNumber(String value) {
public PhoneNumber {
// Прибираємо все крім цифр
value = value.replaceAll("[^0-9+]", "");
}
}
3. Нормалізація з mutable об’єктами:
public record TagCloud(List<String> tags) {
public TagCloud {
// Defensive copy
tags = new ArrayList<>(tags);
// Нормалізація
tags.replaceAll(String::toLowerCase);
tags.sort(null);
}
}
🔴 Senior Level
Internal Implementation
Десугаризація:
// Вихідний код
public record User(String name, int age) {
public User {
if (age < 0) throw new IllegalArgumentException();
name = name.toUpperCase();
}
}
// Десугаризований код
public final class User extends Record {
private final String name;
private final int age;
public User(String name, int age) {
// Код з компактного конструктора
if (age < 0) throw new IllegalArgumentException();
name = name.toUpperCase();
// Присвоювання полів (генерується компілятором)
this.name = name;
this.age = age;
}
}
Обмеження:
- Параметри компактного конструктора — effectively final, але не final
- Можна змінити параметри перед присвоюванням
- Не можна звертатися до
thisдо присвоювання // Оскільки сигнатура канонічного конструктора не містить throws, // checked exception потрібно загорнути в unchecked. - Не можна викинути checked exception (бо канонічний конструктор не declared throws)
Архітектурні Trade-offs
Компактний конструктор vs повний конструктор:
| Аспект | Компактний | Повний |
|---|---|---|
| Валідація | ✅ Ідеальний | ✅ Підходить |
| Нормалізація | ✅ Підходить | ✅ Підходить |
| Зміна параметрів | ✅ Можна | ✅ Можна |
| Делегування | ❌ Не можна | ✅ Можна |
| Кілька конструкторів | ❌ Один | ✅ Кілька |
Edge Cases
1. Нормалізація mutable об’єктів:
public record DateRange(LocalDate start, LocalDate end) {
public DateRange {
Objects.requireNonNull(start);
Objects.requireNonNull(end);
if (start.isAfter(end)) {
// Авто-корекція
var temp = start;
start = end;
end = temp;
}
}
}
2. Компактний конструктор з checked exception:
public record JsonData(String json) {
public JsonData {
try {
Json.parse(json);
} catch (JsonParseException e) {
// ❌ Не можна викинути checked exception
// ✅ Wrap в unchecked
throw new IllegalArgumentException("Invalid JSON", e);
}
}
}
3. Компактний конструктор + додатковий конструктор:
public record Point(int x, int y) {
// Компактний — для валідації
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Negative coordinates");
}
}
// Додатковий — для зручності
public Point(int value) {
this(value, value); // викликає канонічний (з валідацією)
}
}
Продуктивність
Компактний конструктор:
- Zero overhead — код inline в канонічний конструктор
- JIT може оптимізувати валідацію
- Жодних додаткових викликів
Production Experience
Value objects з інваріантами:
public record Range(int min, int max) {
public Range {
if (min > max) {
throw new IllegalArgumentException(
"Min (%d) cannot be greater than max (%d)".formatted(min, max));
}
}
public boolean contains(int value) {
return value >= min && value <= max;
}
}
// Використання
Range r = new Range(1, 10); // OK
Range bad = new Range(10, 1); // throws IllegalArgumentException
DDD Value Objects:
public record UserId(UUID value) {
public UserId {
Objects.requireNonNull(value, "UserId cannot be null");
}
public static UserId generate() {
return new UserId(UUID.randomUUID());
}
public static UserId of(String value) {
return new UserId(UUID.fromString(value));
}
}
public record Email(String value) {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
public Email {
Objects.requireNonNull(value, "Email cannot be null");
value = value.toLowerCase().trim();
if (!EMAIL_PATTERN.matcher(value).matches()) {
throw new IllegalArgumentException("Invalid email format");
}
}
}
Best Practices
// ✅ Використовуйте компактний конструктор для валідації
public record Age(int value) {
public Age {
if (value < 0 || value > 150) {
throw new IllegalArgumentException("Invalid age");
}
}
}
// ✅ Для нормалізації mutable об'єктів
public record Tags(Set<String> tags) {
public Tags {
tags = Set.copyOf(tags); // immutable copy
}
}
// ❌ Не використовуйте для складної логіки
// ❌ Не викидайте checked exceptions
// ❌ Не звертайтеся до this до присвоювання
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- Компактний конструктор — форма без сигнатури, тільки для валідації та нормалізації
- Код компактного конструктора вбудовується в канонічний ПЕРЕД присвоюванням полів
- Параметри можна змінювати:
value = value.toLowerCase().trim()— це змінить значення до присвоювання - Не можна використовувати
this.field = ...— тільки ім’я параметра - Checked exceptions не можна викинути напряму (canonical конструктор не declared throws)
- Не можна додати поля в компактному конструкторі
Часті уточнюючі запитання:
- Навіщо потрібен компактний конструктор? — Для валідації та нормалізації даних без boilerplate
- Чи можна змінити значення параметра? — Так, це normal practice:
value = value.trim() - Чому не можна використовувати this.field? — Присвоювання полів генерується автоматично після компактного конструктора
- Чи можна викинути checked exception? — Ні, потрібно загорнути в unchecked (IllegalArgumentException)
Червоні прапорці (НЕ говорити):
- ❌ “Компактний конструктор присвоює поля” — Присвоювання автоматичне, тільки параметри можна змінювати
- ❌ “Компактний конструктор має сигнатуру” — Немає сигнатури, компілятор визначає за формою
- ❌ “Можна створити додаткове поле” — Instance поля заборонені в Record
- ❌ “Можна викинути IOException з компактного конструктора” — Тільки unchecked exceptions
Пов’язані теми:
- [[1. Що таке Record в Java і з якої версії вони доступні]]
- [[6. Чи можна перевизначити конструктор в Record]]
- [[8. Чи можна оголошувати статичні поля та методи в Record]]
- [[9. Чи є поля Record фінальними]]