Почему иммутабельный класс должен быть final?
Иммутабельный класс должен быть final, чтобы никто не мог создать его подкласс и добавить изменяемые поля или методы.
Junior Level
Иммутабельный класс должен быть final, чтобы никто не мог создать его подкласс и добавить изменяемые поля или методы.
public final class Point { // final — нельзя наследоваться
private final int x;
private final int y;
// ...
}
Если убрать final, кто-то может написать:
public class MutablePoint extends Point {
private int z; // новое поле
public void setZ(int z) { this.z = z; } // мутация!
}
Middle Level
Защита от подмены поведения
Без final подкласс может:
- Добавить мутабельное состояние — новые поля с сеттерами
- Переопределить геттеры — возвращать другие данные
- Переопределить equals/hashCode — сломать работу в
HashMap
Сценарий атаки
public void process(ImmutablePoint point) {
validate(point); // проверяем координаты
// здесь подкласс может изменить данные
saveToDatabase(point); // сохраняем уже изменённые данные
}
Примеры из JDK
public final class String { ... }
public final class Integer { ... }
public final class BigDecimal { ... }
public final class LocalDate { ... }
Что если нужно наследование?
Используйте Sealed classes (Java 17+) для контроля наследников, или композицию.
Или использовать sealed классы (Java 17+), которые ограничивают круг наследников.
Когда final может мешать
- Mocking в тестах: Mockito не может моковать final-классы без bytebuddy-агента
- Прокси-фреймворки: некоторые требуют наследования (Spring AOP через CGLIB)
- Решение: sealed classes или Mockito inline-mock-maker
Senior Level
Гарантии JMM и JIT
final class + final fields создаёт “непробиваемую” оболочку:
- JMM:
finalполя обеспечивают safe publication с memory barrier - JIT: компилятор может агрессивно инлайнить методы и кэшировать значения, зная что класс не будет переопределён
Проблемы без final
- HashMap corruption — подкласс может переопределить
hashCode(), возвращая разные значения - Security vulnerabilities — TOCTOU-атаки через подмену результата геттера
- Thread-safety loss — подкласс обращается к не-
finalполям без синхронизации
Резюме для Senior
finalкласс предотвращает изменение логики через переопределение методов- Без
finalнельзя гарантировать потокобезопасность и стабильность хеш-кода final class— сигнал другим разработчикам и компилятору о неизменном контракте- Для контролируемого наследования используйте
sealedклассы (Java 17+)
🎯 Шпаргалка для интервью
Обязательно знать:
finalзапрещает наследование — подкласс не добавит мутабельность- Без
finalподкласс может: добавить mutable поля, переопределить геттеры, сломать equals/hashCode - HashMap corruption — подкласс переопределяет
hashCode(), объект теряется - Security vulnerabilities — TOCTOU-атаки через подмену результата геттера
- JMM:
final class + final fields= непробиваемая оболочка с safe publication - Когда final мешает: mocking в тестах, прокси-фреймворки (CGLIB); решение: sealed classes или inline-mock-maker
Частые уточняющие вопросы:
- Что будет без final? — Подкласс добавит mutable поля, сломает hashCode и thread-safety
- Mockito не может мокать final-классы? — Нужен inline-mock-maker или bytebuddy-агент
- Sealed classes как альтернатива? — Да, Java 17+: ограничивают круг наследников
- JIT выигрывает от final? — Да: агрессивный inlining, кэширование, т.к. класс не переопределят
Красные флаги (НЕ говорить):
- «Без final иммутабельность работает» — подкласс может всё сломать
- «Final нужен только для стиля» — это гарантия безопасности и JIT-оптимизаций
- «Mockito не работает с final» — работает с inline-mock-maker
- «Sealed classes = final» — sealed разрешает наследование от конкретных классов
Связанные темы:
- [[7. Что такое ключевое слово final и как оно помогает в создании иммутабельных классов]]
- [[15. Можно ли наследоваться от иммутабельного класса]]
- [[17. Что произойдёт, если переопределить геттер в подклассе иммутабельного класса]]
- [[1. Что такое иммутабельный (неизменяемый) объект]]