Як створити незмінний клас в Java?
Щоб створити незмінний клас, дотримуйтесь цих кроків:
Junior Level
Щоб створити незмінний клас, дотримуйтесь цих кроків:
Крок 1: Оголосіть клас як final
public final class Person {
Крок 2: Зробіть усі поля private final
private final String name;
private final int age;
Крок 3: Не створюйте сетери
Тільки гетери (методи для читання):
public String getName() { return name; }
public int getAge() { return age; }
Крок 4: Ініціалізуйте поля в конструкторі
public Person(String name, int age) {
this.name = name;
this.age = age;
}
Повний приклад
public final class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
Коли НЕ створювати незмінний клас
- JPA-entities — зазвичай змінні (ORM вимагає setters, no-arg constructor)
- Прості скрипти/прототипи — швидкість розробки важливіша
- DTO для серіалізації — частіше зручні mutable з setters
Middle Level
Робота зі змінними полями
Якщо клас містить колекції або інші об’єкти, що змінюються, потрібно робити захисні копії:
public final class ImmutableReport {
private final String title;
private final List<String> items;
public ImmutableReport(String title, List<String> items) {
this.title = title;
this.items = (items == null) ? List.of() : List.copyOf(items);
}
public String getTitle() { return title; }
public List<String> getItems() {
return items; // List.copyOf вже повернув незмінний список
}
}
Реалізація через Record (Java 14+)
public record ImmutableReport(String title, List<String> items) {
public ImmutableReport {
items = List.copyOf(items); // компактний конструктор
}
}
record автоматично:
- Робить клас
final - Робить поля
private final - Генерує конструктор,
equals(),hashCode(),toString()
П’ять правил Джошуа Блоха
- Не надавайте методи-мутатори
- Забезпечте неможливість розширення класу (
final) - Зробіть усі поля
final - Зробіть усі поля
private - Забезпечте ексклюзивний доступ до змінних компонентів
Senior Level
Небезпека витоку “this” (Constructor Escape)
public ImmutableClass() {
ExternalService.register(this); // ПОМИЛКА!
}
Якщо інший потік звернеться до об’єкта до завершення конструктора, він побачить final поля у дефолтному стані.
Глибоке копіювання (Deep Copy)
Якщо список містить змінні об’єкти, List.copyOf недостатньо:
public record Order(Long id, List<Item> items) {
public Order {
items = items.stream()
.map(original -> new Item(original)) // Замість clone() (який вважається broken — Effective Java Item 13): конструктор копіювання — надійніше
.toList();
}
}
Reflection атака
Починаючи з Java 9 (Project Jigsaw) і особливо з Java 16+ (JEP 396) доступ через reflection до java.base полів заблоковано за замовчуванням.
Резюме для Senior
- Звичайного
finalнедостатньо для змінних посилальних типів - Завжди використовуйте
List.copyOf()абоCollections.unmodifiableList() - Пам’ятайте про Constructor Escape
- Для простих DTO обирайте Records
- Глибока незмінність вимагає копіювання всієї ієрархії об’єктів
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- 4 кроки: final class, private final поля, немає сетерів, ініціалізація в конструкторі
- П’ять правил Джошуа Блоха: немає мутаторів, немає розширення, final поля, private поля, захист змінних компонентів
- Для змінних полів —
List.copyOf()в конструкторі та повернення незмінної обгортки з гетера - Records (Java 14+) автоматично роблять клас final, поля private final, генерують equals/hashCode/toString
- Constructor Escape — не можна передавати
thisназовні під час конструктора - Deep Copy: якщо список містить змінні об’єкти, потрібно клонувати кожен елемент
- Коли НЕ створювати: JPA-entities, прості прототипи, DTO для серіалізації
Часті уточнюючі запитання:
- Що робити з колекціями в конструкторі? —
List.copyOf(input)створює незалежну копію - Records vs звичайний клас? — Records: мінімум коду, але немає успадкування; звичайний клас: повний контроль
- Що таке Constructor Escape? — Публікація
thisдо завершення конструктора = інший потік побачить null-поля - Чи достатньо List.copyOf для глибокої незмінності? — Ні, якщо елементи списку змінні
Червоні прапорці (НЕ говорити):
- «Просто final полів — і все» — без defensive copy змінні поля вразливі
- «Можна передати this в конструкторі для реєстрації» — це Constructor Escape
- «List.copyOf копіює елементи» — копіюється лише контейнер, елементи ті самі
- «Record вирішує всі проблеми» — колекції в Records теж потрібно копіювати вручну
Пов’язані теми:
- [[1. Що таке незмінний (ім’ютабельний) об’єкт]]
- [[7. Що таке ключове слово final і як воно допомагає у створенні незмінних класів]]
- [[8. Чи достатньо зробити всі поля final для незмінності]]
- [[9. Що робити, якщо поле класу посилається на змінний об’єкт]]
- [[20. Що таке Record і як він допомагає створювати незмінні класи]]