Какие опасности есть при использовании внутренних классов в Java
Что проверяют
Понимаете ли вы, чем внутренний класс отличается от статического вложенного и во что он превращается после компиляции. Главная ожидаемая тема: скрытая ссылка на внешний объект. Следом спрашивают, почему вложенные классы советуют по умолчанию делать static.
Как отвечать
Сначала механика. Экземпляр внутреннего класса всегда привязан к экземпляру внешнего: компилятор добавляет скрытое поле со ссылкой на Outer.this и передаёт её через конструктор. Анонимные и локальные классы в нестатическом контексте устроены так же. Отсюда опасности:
- утечки памяти: внутренний объект держит внешний. Слушатель, колбэк или задача в пуле, которые живут дольше владельца, не дают сборщику убрать внешний объект со всеми полями. Классика: анонимный
RunnableилиHandlerвнутриActivityна Android; - двойные фигурные скобки:
new HashMap<>() {{ put("a", 1); }}создаёт анонимный подкласс со ссылкой на внешний объект и отдельный класс на каждое такое место; - сериализация: вместе с внутренним объектом сериализуется и внешний, а если он не
Serializable, получаетеNotSerializableException. Сериализовать внутренние классы документация прямо не рекомендует: синтетические поля зависят от компилятора; - доступ к приватным членам: до Java 11 обращение к
privateполям внешнего класса шло через синтетические методы с пакетной видимостью, и приватность ослаблялась на весь пакет. С Java 11 это решают nestmates; - затенение имён: поле внутреннего класса с тем же именем прячет поле внешнего, приходится писать
Outer.this.field.
Вывод: если классу не нужен экземпляр внешнего, объявляйте его static. Лямбда, которая не трогает поля и методы экземпляра, this не захватывает.
Где спотыкаются
Не могут объяснить, откуда утечка, и говорят общее «внутренние классы опасны». Нужно назвать конкретную ссылку Outer.this.
Считают лямбду полной заменой анонимного класса. В лямбде this указывает на внешний объект, а в анонимном классе на сам анонимный объект.
Уверены, что во внутреннем классе нельзя объявить static поле. Это ограничение сняли в Java 16 вместе с появлением records.