Чи достатньо зробити всі поля final для забезпечення незмінності?
Якщо клас складається лише з примітивів (int, double, boolean) та незмінних типів (String, BigDecimal, LocalDate), то final для всіх полів + final для класу — достатньо.
Junior Level
Ні, недостатньо! final захищає лише саму змінну (посилання), але не дані, на які вона вказує.
Приклад проблеми
public final class NotReallyImmutable {
private final List<String> items; // поле final
public NotReallyImmutable(List<String> items) {
this.items = items; // зберегли пряме посилання!
}
public List<String> getItems() {
return items; // повернули пряме посилання!
}
}
List<String> myItems = new ArrayList<>();
myItems.add("A");
NotReallyImmutable obj = new NotReallyImmutable(myItems);
myItems.add("B"); // Змінили "незмінний" об'єкт ззовні!
// this.items = items зберігає те саме посилання, що й myItems.
// Тому myItems.add("B") змінює список всередині об'єкта.
obj.getItems().clear(); // Ще й через гетер можна змінювати!
Що ще потрібно
- Клас повинен бути
final - Робити копії колекцій в конструкторі
- Повертати копії (або незмінні обгортки) з гетерів
final поля vs незмінний клас
| final поля | Незмінний клас | |
|---|---|---|
| Поля не можна переassign’ити | ✅ | ✅ |
| Змінні поля захищені | ❌ | ✅ (defensive copy) |
| Успадкування заборонено | ❌ | ✅ (final class) |
| Гетери повертають копії | ❌ | ✅ |
Middle Level
Shallow Immutability vs Deep Immutability
Shallow (поверхнева) — final поля, але вміст змінних об’єктів можна змінити:
private final List<User> users; // список не можна замінити, але можна змінити елементи
Deep (глибока) — все, включаючи вкладені об’єкти, незмінне:
public final class Group {
private final List<String> names; // String незмінний — глибинний захист забезпечено
public Group(List<String> names) {
this.names = List.copyOf(names); // копія
}
public List<String> getNames() {
return names; // List.copyOf вже повернув незмінний список
}
}
Коли final достатньо
Якщо клас складається лише з примітивів (int, double, boolean) та незмінних типів (String, BigDecimal, LocalDate), то final для всіх полів + final для класу — достатньо.
Коли final недостатньо
- Масиви — можна змінювати елементи
- Колекції (
List,Map,Set) — можна змінювати вміст Date— має методиsetTime(),setYear()тощо- Кастомні змінні класи
Senior Level
Reflection Attack
Навіть “ідеально” незмінний клас можна спробувати змінити через Reflection:
Field field = MyClass.class.getDeclaredField("value");
field.setAccessible(true);
field.set(obj, newValue); // ламає незмінність
Починаючи з Java 9 (Project Jigsaw) модульна система обмежує reflection, а з Java 16 (JEP 396) доступ до полів java.base заблоковано за замовчуванням.
Повна формула незмінності
final class + final fields + defensive copies + no this-escape = True Immutability
Резюме для Senior
finalзахищає лише посилання, але не дані за посиланням- Для колекцій та масивів обов’язково використовуйте захисне копіювання
- Незмінність — властивість всієї ієрархії об’єктів, а не лише верхнього рівня
- Пам’ятайте:
final fields + final class + defensive copies = Immutable - В Java 14+ використовуйте
record— він застосовує більшість правил автоматично
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
finalзахищає лише посилання, але не дані за посиланням- Shallow Immutability — final поля, але вміст змінних об’єктів можна змінити
- Deep Immutability — все, включаючи вкладені об’єкти, незмінне
- Формула:
final class + final fields + defensive copies + no this-escape = True Immutability - Коли final достатньо: примітиви + незмінні типи (String, BigDecimal, LocalDate)
- Коли final НЕ достатньо: масиви, колекції, Date, кастомні змінні класи
- Reflection Attack: навіть perfect immutable class можна змінити через reflection (до Java 16)
Часті уточнюючі запитання:
- Чому final List не захищає? —
list.add()змінює вміст, посилання те саме - Коли final достатньо? — Лише якщо клас складається з примітивів та незмінних типів
- Що таке Shallow vs Deep Immutability? — Shallow: контейнер незмінний, елементи — ні; Deep: все незмінне
- Reflection може зламати незмінність? — Так, але в Java 16+ доступ до java.base полям заблоковано
Червоні прапорці (НЕ говорити):
- «Final поля = незмінний клас» — без defensive copy та final class недостатньо
- «List.copyOf не потрібен якщо поле final» — final захищає посилання, не вміст
- «clone() — найкращий спосіб копіювання» — clone() вважається broken (Effective Java Item 13)
- «Reflection — не проблема» — до Java 16 це реальна загроза
Пов’язані теми:
- [[1. Що таке незмінний (ім’ютабельний) об’єкт]]
- [[7. Що таке ключове слово final і як воно допомагає у створенні незмінних класів]]
- [[9. Що робити, якщо поле класу посилається на змінний об’єкт]]
- [[10. Що таке захисна копія (defensive copy)]]
- [[14. В чому різниця між shallow copy та deep copy]]