Что такое компактный конструктор (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 финальными]]