Вопрос 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]]