Що станеться, якщо перевизначити геттер в підкласі незмінного класу?
Якщо незмінний клас не final, підклас може перевизначити гетер і повертати інші дані. Це зламає всі очікування коду, який розраховує на незмінність.
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
Чому це небезпечно
-
Порушення логіки колекцій — якщо об’єкт в
HashMapі його гетер (що бере участь уequals()) повертає інші дані — об’єкт стане неможливо знайти -
Уразливості в безпеці — метод безпеки перевіряє
getData()на заборонені символи, а підклас підміняє результат після перевірки -
Втрата потокобезпеки — гетер підкласу звертається до не-
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());
}
}
Рішення
finalклас — повністю забороняє наслідуванняsealedклас (Java 17+) — дозволяє лише конкретні, перевірені нащадки- Композиція — замість наслідування
Резюме для 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]]