Что произойдёт, если переопределить геттер в подклассе иммутабельного класса?
Если иммутабельный класс не 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]]