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

Що станеться, якщо перевизначити геттер в підкласі незмінного класу?

Якщо незмінний клас не final, підклас може перевизначити гетер і повертати інші дані. Це зламає всі очікування коду, який розраховує на незмінність.

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

Junior Level

Якщо незмінний клас не final, підклас може перевизначити гетер і повертати інші дані. Це зламає всі очікування коду, який розраховує на незмінність.

public class ImmutableBase {  // не final — помилка
    private final String data;
    public ImmutableBase(String data) { this.data = data; }
    public String getData() { return data; }
}

public class MutableChild extends ImmutableBase {
    private String dynamicData;
    public void setDynamicData(String d) { this.dynamicData = d; }

    @Override
    public String getData() {
        // Зверніть увагу: батьківське final поле data все ще незмінне,
        // але getData() повертає інше значення.
        return dynamicData; // підмінили результат!
    }
}

Middle Level

Чому це небезпечно

  1. Порушення логіки колекцій — якщо об’єкт в HashMap і його гетер (що бере участь у equals()) повертає інші дані — об’єкт стане неможливо знайти

  2. Уразливості в безпеці — метод безпеки перевіряє getData() на заборонені символи, а підклас підміняє результат після перевірки

  3. Втрата потокобезпеки — гетер підкласу звертається до не-final полів без синхронізації → “брудне читання” в багатопотоковому середовищі

Як захиститися

Завжди робіть незмінні класи final:

public final class ImmutableBase { ... } // наслідування неможливе

Навіть з final класом гетер може повернути мутабельний об’єкт — це інша форма порушення незмінності, див. файл 29.


Senior Level

Глибинна проблема

Перевизначення гетера руйнує контракт незмінності на рівні бізнес-логіки, навіть якщо final поля технічно не змінилися. JMM гарантує видимість final полів, але не може гарантувати, що метод повертатиме саме ці поля.

Приклад реального світу

public void authorize(ImmutableCredentials creds) {
    if (securityManager.check(creds.getToken())) {
        // Тут підклас може повернути інший токен
        api.call(creds.getToken());
    }
}

Рішення

  1. final клас — повністю забороняє наслідування
  2. sealed клас (Java 17+) — дозволяє лише конкретні, перевірені нащадки
  3. Композиція — замість наслідування

Резюме для Senior

  • Перевизначення гетера руйнує гарантії незмінності через поліморфізм
  • JMM не захищає від підміни логіки методів
  • Завжди final для незмінних класів
  • sealed класи — для контрольованого наслідування

Шпаргалка для інтерв’ю

Обов’язково знати:

  • Підклас може перевизначити гетер і повертати інші дані — контракт незмінності зруйновано
  • Порушення логіки колекцій: об’єкт в HashMap стане ненайденим, якщо гетер бере участь в equals
  • Уразливості в безпеці: гетер підміняє результат після перевірки (TOCTOU)
  • Втрата потокобезпеки: гетер підкласу звертається до не-final полів без синхронізації
  • JMM гарантує видимість final полів, але не логіку методів
  • Рішення: final клас (повна заборона), sealed клас (контрольоване наслідування), композиція

Часті уточнювальні запитання:

  • Final поля батька не змінилися — в чому проблема? — Гетер повертає ІНШІ дані, ламається контракт
  • Приклад реальної уразливості? — authorize() перевіряє токен, підклас повертає інший токен при збереженні
  • Як JMM відноситься до цього? — JMM захищає дані final полів, але не може гарантувати, що метод поверне саме їх
  • sealed class вирішує проблему? — Так, якщо всі permitted-класи теж незмінні

Червоні прапорці (НЕ говорити):

  • «Гетер підкласу не небезпечний — final поля ті ж» — гетер може повернути що завгодно
  • «Це проблема тільки для HashMap» — ні, це security, thread-safety і бізнес-логіка
  • «Можна перевірити гетер у тестах» — поліморфізм дозволяє підмінити в runtime
  • «Композиція завжди гірша за наслідування» — для незмінності композиція — єдиний безпечний шлях

Пов’язані теми:

  • [[15. Чи можна наслідуватися від незмінного класу]]
  • [[16. Чому незмінний клас повинен бути final]]
  • [[7. Що таке ключове слово final і як воно допомагає у створенні незмінних класів]]
  • [[28. Що станеться, якщо змінити змінний ключ в HashMap]]