Какие свойства у вложенного класса, зависящего от экземпляра родительского класса в Java
Что проверяют
Понимаете ли вы, что внутренний (нестатический вложенный) класс держит скрытую ссылку на объект внешнего класса и чем это оборачивается. Следом почти всегда спрашивают, чем он отличается от static вложенного класса и какой из них выбирать по умолчанию.
Как отвечать
Внутренний класс объявлен внутри другого класса без модификатора static. Его ключевые свойства:
- скрытая ссылка на внешний объект: компилятор добавляет поле с экземпляром
Outerи передаёт его в конструктор, поэтомуInnerне существует безOuter; - создание: внутри методов экземпляра просто
new Inner(), снаружиouter.new Inner(). Из статического метода без объекта внешнего класса его не создать; - доступ ко всем членам внешнего класса, включая
private. При совпадении имён к полю внешнего объекта обращаются черезOuter.this.field; - статические члены: до Java 16 разрешались только константы
static final, с Java 16 внутренний класс может объявлять обычные статические поля и методы; - модификаторы доступа: как у любого члена класса, от
privateдоpublic; - байткод: отдельный файл
Outer$Inner.class. С Java 11 доступ кprivateчленам идёт через механизм nestmates, без синтетических методов-мостов.
Локальные и анонимные классы тоже внутренние. Они дополнительно захватывают локальные переменные, которые обязаны быть effectively final.
Когда уместен: итератор коллекции, обработчик, которому нужно состояние владельца. Если доступ к полям внешнего объекта не нужен, делайте класс static. Это рекомендация из Effective Java, и она прямо про память.
Где спотыкаются
Не упоминают утечку памяти. Внутренний объект, отданный в долгоживущий кеш, поток или слушатель, держит внешний объект со всем его графом, и сборщик мусора не может его освободить.
Путают термины: называют внутренним любой вложенный класс, хотя static вложенный класс ссылки на внешний объект не имеет и создаётся как new Outer.Nested().
Забывают про сериализацию. Сериализовать внутренние классы не рекомендуется: скрытое поле тянет за собой внешний объект, а имена синтетических полей зависят от компилятора.