Вопрос 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]]