Чому незмінний клас повинен бути 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+) для контролю нащадків, або композицію.
Коли 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. Що таке незмінний (immutability) об’єкт]]