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

Чому незмінний клас повинен бути final?

Незмінний клас повинен бути final, щоб ніхто не міг створити його підклас і додати змінювані поля або методи.

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

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 підклас може:

  1. Додати мутабельний стан — нові поля з сетерами
  2. Перевизначити гетери — повертати інші дані
  3. Перевизначити 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

  1. HashMap corruption — підклас може перевизначити hashCode(), повертаючи різні значення
  2. Security vulnerabilities — TOCTOU-атаки через підміну результату гетера
  3. 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) об’єкт]]