Чи можна перевизначити конструктор в Record
Structured Java interview answer with junior, middle, and senior-level explanation.
🟢 Junior Level
Так, але з обмеженнями. В Record можна додати свій конструктор, але він зобов’язаний викликати канонічний конструктор (через this(...)).
Два способи:
// Спосіб 1: Повна форма
public record User(String name, int age) {
// Свій конструктор — повинен викликати this(...)
public User(String name) {
this(name, 0); // виклик канонічного конструктора
}
}
// Спосіб 2: Компактний конструктор (тільки валідація)
public record User(String name, int age) {
public User {
if (age < 0) {
throw new IllegalArgumentException("Invalid age");
}
}
}
Не можна:
- ❌ Створити конструктор, який не викликає
this(...) - ❌ Додати конструктор без виклику канонічного
🟡 Middle Level
Як це працює
1. Канонічний конструктор (автогенерований):
public record Point(int x, int y) {}
// Автогенерований:
public Point(int x, int y) {
this.x = x;
this.y = y;
}
2. Додатковий конструктор:
public record Point(int x, int y) {
// Повинен викликати this(...) в першому рядку
public Point() {
this(0, 0); // ✅ OK
}
public Point(int value) {
this(value, value); // ✅ OK
}
// ❌ public Point(String s) { } — немає this()!
}
3. Компактний конструктор (тільки валідація):
public record Email(String value) {
// Компактний конструктор — НЕ містить сигнатури
public Email {
if (value == null || !value.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
}
// Використання
Email e1 = new Email("test@example.com"); // ✅ OK
Email e2 = new Email("invalid"); // throws IllegalArgumentException
Типові помилки
- Забули викликати
this(...):public record User(String name) { // ❌ compilation error — немає виклику this() public User() { System.out.println("Created"); } // ✅ Правильно public User() { this("Anonymous"); } } - Компактний конструктор з присвоюванням:
public record User(String name) { // Компактний конструктор МОЖЕ присвоювати параметри: // name = name.toUpperCase() — валідно в Java 16+. // Присвоювання відбувається перед implicit field assignment. public User { name = name.toUpperCase(); // OK в Java 16+ } }
Практичне застосування
1. Валідація:
public record Money(BigDecimal amount, Currency currency) {
public Money {
Objects.requireNonNull(currency, "Currency cannot be null");
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Negative amount");
}
}
}
2. Нормалізація даних:
public record Email(String value) {
public Email {
value = value.toLowerCase().trim(); // нормалізація
}
}
3. Фабричні конструктори:
public record Range(int min, int max) {
public Range(int value) {
this(value, value);
}
public Range(String rangeStr) {
// parsing "1-100" -> min=1, max=100
String[] parts = rangeStr.split("-");
this(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
}
}
🔴 Senior Level
Internal Implementation
Компілятор генерує:
// Вихідний код
public record User(String name, int age) {
public User {
if (age < 0) throw new IllegalArgumentException();
}
public User(String name) {
this(name, 0);
}
}
// Десугарized:
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();
this.name = name;
this.age = age;
}
// Додатковий конструктор
public User(String name) {
this(name, 0);
}
}
Архітектурні Trade-offs
Компактний конструктор vs повний конструктор:
| Підхід | Плюси | Мінуси |
|---|---|---|
| Компактний | Короткий, тільки валідація | Не можна змінити параметри |
| Повний | Гнучкість, можна змінювати параметри | Більше коду, ризик помилок |
Edge Cases
1. Ланцюжок конструкторів:
public record Config(String host, int port, String protocol) {
public Config(String host, int port) {
this(host, port, "http");
}
public Config(String host) {
this(host, 8080);
}
public Config() {
this("localhost");
}
public Config {
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("Invalid port");
}
}
}
2. Нормалізація з mutable об’єктами:
public record User(List<String> emails) {
public User {
// Defensive copy для mutable колекцій
emails = new ArrayList<>(emails);
}
}
3. Конструктор з винятком:
public record JsonNode(String json) {
public JsonNode {
try {
// Валідація JSON
Json.parse(json);
} catch (JsonParseException e) {
throw new IllegalArgumentException("Invalid JSON", e);
}
}
}
Продуктивність
Конструктори Records:
- Компактний конструктор: inline валідація
- Повний конструктор: делегування (один виклик)
- Різниця negligible (< 1 ns)
- JIT інлайнить обидва підходи
Production Experience
DTO з валідацією:
public record CreateUserRequest(
@NotBlank String name,
@Email String email,
@Min(18) int age
) {
public CreateUserRequest {
// Додаткова валідація після Bean Validation
if (name.length() > 100) {
throw new IllegalArgumentException("Name too long");
}
}
}
Domain objects:
public record OrderId(String value) {
private static final Pattern UUID_PATTERN =
Pattern.compile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
public OrderId {
if (!UUID_PATTERN.matcher(value).matches()) {
throw new IllegalArgumentException("Invalid UUID format");
}
value = value.toLowerCase(); // нормалізація
}
public static OrderId generate() {
return new OrderId(UUID.randomUUID().toString());
}
}
Best Practices
// ✅ Компактний конструктор для валідації
public record Email(String value) {
public Email {
Objects.requireNonNull(value);
if (!value.contains("@")) throw new IllegalArgumentException();
}
}
// ✅ Повний конструктор для зручності
public record Point(int x, int y) {
public Point() { this(0, 0); }
public Point(int v) { this(v, v); }
}
// ❌ Не мутуйте поля після присвоювання
// ❌ Не намагайтеся обійти валідацію
// ❌ Не додавайте побічні ефекти в конструктор
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- В Record можна додати свій конструктор, але він зобов’язаний викликати канонічний через
this(...) - Компактний конструктор — форма без сигнатури, тільки для валідації та нормалізації
- Компактний конструктор вбудовується в канонічний перед присвоюванням полів
- В компактному конструкторі можна змінювати параметри перед присвоюванням:
name = name.toUpperCase() - Checked exceptions не можна викинути з компактного конструктора (canonical не declared throws)
- Можна мати кілька додаткових конструкторів + один компактний
Часті уточнюючі запитання:
- Чим компактний конструктор відрізняється від звичайного? — Компактний не має сигнатури, тільки валідація; звичайний повинен викликати
this(...) - Чи можна нормалізувати дані в конструкторі? — Так, в компактному можна:
name = name.trim() - Що буде якщо конструктор не викличе this(…)? — Compilation error: “constructor must call this(…)”
- Чи можна викинути checked exception з компактного конструктора? — Ні, потрібно загорнути в unchecked
Червоні прапорці (НЕ говорити):
- ❌ “Компактний конструктор присвоює поля через this.field” — Використовується ім’я параметра, не this.field
- ❌ “Можна створити конструктор без виклику this()” — Обов’язковий виклик канонічного конструктора
- ❌ “Компактний конструктор може додати поле” — Не можна додати instance поле
- ❌ “Конструктор може викинути checked exception” — Канонічний конструктор не declared throws
Пов’язані теми:
- [[1. Що таке Record в Java і з якої версії вони доступні]]
- [[4. Чи можна додавати додаткові методи в Record]]
- [[7. Що таке компактний конструктор (compact constructor) в Record]]
- [[9. Чи є поля Record фінальними]]