Вопрос 7 · Раздел 13

Что такое ключевое слово final и как оно помогает в создании иммутабельных классов?

Ключевое слово final в Java имеет три значения в зависимости от контекста:

Версии по языкам: English Russian Ukrainian

Junior Level

Ключевое слово final в Java имеет три значения в зависимости от контекста:

  • final для переменной = нельзя переassign’ить
  • final для метода = нельзя переопределить в подклассе
  • final для класса = нельзя наследоваться

1. final переменная

final int MAX = 100;
// MAX = 200; // ОШИБКА! Нельзя переassign'ить

2. final метод

public final void doSomething() {
    // Этот метод нельзя переопределить в подклассе
}

3. final класс

public final class MyImmutableClass {
    // От этого класса нельзя наследоваться
}

Для иммутабельных классов

Используйте final для класса и для всех полей:

public final class Point {
    private final int x;
    private final int y;
    // ...
}

Когда final на поле мешает

final несовместим с lazy initialization — если значение вычисляется при первом обращении, поле не может быть final. Используйте holder pattern или AtomicReference.


Middle Level

final на уровне класса

Объявление класса как final запрещает наследование. Это критически важно для иммутабельности: подкласс не сможет добавить мутабельные поля или переопределить геттеры.

final на уровне полей

Поле final можно инициализировать только один раз — при объявлении или в конструкторе:

public final class Config {
    private final String url;  // инициализируется в конструкторе
    private final int timeout = 30; // или сразу при объявлении

    public Config(String url) {
        this.url = url;
    }
}

final не защищает содержимое

private final List<String> list = new ArrayList<>();
list.add("New Item"); // РАЗРЕШЕНО! Ссылка та же, содержимое изменилось

Поэтому для коллекций нужно дополнительное защитное копирование.

final методы

Если класс не final, но отдельные методы final — это не обеспечивает полную иммутабельность, но защищает конкретные методы от переопределения.


Senior Level

Safe Publication и JMM гарантии

final обеспечивает “заморозку” (freeze) значения в конце конструктора. Любой поток, увидевший ссылку на объект, гарантированно увидит его final поля в инициализированном состоянии. Без final возможна ситуация, когда поток видит объект, но его поля ещё содержат значения по умолчанию (null или 0) из-за переупорядочивания инструкций процессором.

JIT-оптимизации

JIT-компилятор может агрессивнее оптимизировать код:

  • Inlining — подстановка значений final полей напрямую
  • Constant folding — вычисление выражений на этапе компиляции
  • Hoisting — вынос final полей из циклов, т.к. они не меняются

final и модульная система (Java 9+)

Начиная с Java 16 (JEP 396 — Strong Encapsulation) доступ через reflection к final полям java.base заблокирован по умолчанию. В Java 17 (JEP 403) усилено для всех JDK internals. Требуется --add-opens.

Резюме для Senior

  • final класс предотвращает поломку иммутабельности через наследование
  • final поля обеспечивают атомарную видимость данных в многопоточности (JMM freeze)
  • final ссылка ≠ неизменяемость данных по ссылке — нужна defensive copy
  • JIT использует final для агрессивных оптимизаций

🎯 Шпаргалка для интервью

Обязательно знать:

  • final для переменной = нельзя переassign’ить, для метода = нельзя переопределить, для класса = нельзя наследоваться
  • Для иммутабельности: final класс + final поля = безопасная публикация (JMM freeze)
  • final не защищает содержимое по ссылке — final List можно менять, нужна defensive copy
  • JIT оптимизации: inlining, constant folding, hoisting — всё благодаря final
  • Safe publication: после конструктора все final поля видны любому потоку
  • Начиная с Java 16, reflection к final полям java.base заблокирован (JEP 396)

Частые уточняющие вопросы:

  • Final поля можно инициализировать где? — При объявлении или в конструкторе
  • final класс можно мокать в тестах? — Mockito требует inline-mock-maker или bytebuddy-агент
  • final ссылка = иммутабельные данные? — Нет, final List позволяет add/remove — нужна копия
  • Зачем final метод? — Чтобы подкласс не переопределил и не сломал иммутабельность

Красные флаги (НЕ говорить):

  • «Final полей достаточно для иммутабельности» — без final class и defensive copy недостаточно
  • «final = const» — final нельзя переassign, но объект по ссылке может меняться
  • «final метод делает класс иммутабельным» — только конкретный метод защищён, не весь класс
  • «Reflection может изменить final поле» — в Java 16+ для java.base это невозможно

Связанные темы:

  • [[1. Что такое иммутабельный (неизменяемый) объект]]
  • [[3. Как создать иммутабельный класс в Java]]
  • [[8. Достаточно ли сделать все поля final для иммутабельности]]
  • [[16. Почему иммутабельный класс должен быть final]]