Питання 8 · Розділ 13

Чи достатньо зробити всі поля final для забезпечення незмінності?

Якщо клас складається лише з примітивів (int, double, boolean) та незмінних типів (String, BigDecimal, LocalDate), то final для всіх полів + final для класу — достатньо.

Мовні версії: English Russian Ukrainian

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(); // Ще й через гетер можна змінювати!

Що ще потрібно

  1. Клас повинен бути final
  2. Робити копії колекцій в конструкторі
  3. Повертати копії (або незмінні обгортки) з гетерів

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]]