velikol.ru
1



Java позволяет определять класс внутри другого класса. Класс, определенный внутри внешнего (охватывающего) класса, называется вложенным классом (nested class) – терминология фимы Sun:

  • Java позволяет определять класс внутри другого класса. Класс, определенный внутри внешнего (охватывающего) класса, называется вложенным классом (nested class) – терминология фимы Sun:

        • class OuterClass { ...
        • class NestedClass { … }
        • }
  • Вложенные классы распадаются на две категории:

    • Статические (static) вложенные классы;
    • Нестатические вложенные классы, называемые также внутренними (inner class):
    • class OuterClass { ...
    • static class StaticNestedClass { ... }
    • class InnerClass { ... }
    • }


Любой вложенный класс является членом охватывающего класса и – как таковой – может быть объявлен private, public, protected или быть package private. (Какими могут быть классы, которые не являются вложенными?)

  • Любой вложенный класс является членом охватывающего класса и – как таковой – может быть объявлен private, public, protected или быть package private. (Какими могут быть классы, которые не являются вложенными?)

  • Статические вложенные классы не имеют доступа к другим (т.е.нестатическим) членам охватывающего класса.

  • Нестатические вложенные классы (inner-классы) имеют доступ к другим членам охватывающего класса, включая приватные.

      • (тут у вас могут возникнуть вопросы...- они есть?)
  • Причин для использования вложенных классов несколько, в том числе:

    • Логическая группировка классов (например, нужных не всем);
    • Усиление средств инкапсуляции;
    • Улучшение структуризации кода (читаемость, и пр.).
  • Работу вложенных классов обеспечивает компилятор; для виртуальной машины нет отличия от внешних классов. Для обозначения вложенного класса в виртуальной машине используется символ $ (разделяет имена внешних и внутренних классов ).



Статические вложенные классы

  • Как и другие статические члены класса (методы и поля), статические вложенные классы ассоциированы с охватывающим классом;

  • Как и статические методы, они не могут непосредственно ссылаться на нестатические поля или методы охватывающего класса (без получения ссылки на объект); но могут обращаться к статическим членам охватывающего класса (полям, методам и (всяким!) вложенным классам), включая приватные. В остальном, с позиций поведения, такие классы ничем не отличаются от top-level классов.

  • Обращение к статическим вложенным классам (из вне охватывающего/их) происходит с использованием имени (имен) охватывающего, например:

    • MyOuterClass.MyStaticNestedClass myObject = new MyOuterClass.MyStaticNestedClass();
    • в сущности, имя охватывающего выступает «в роли пакета».


Внутренние классы

  • Как и в случае полей и методов, нестатические вложенные классы (то есть – внутренние, inner) ассоциируются с экземпляром объекта охватывающего класса;

  • При этом имеется прямой доступ к полям и методам этого охватывающего объекта (включая приватные).

  • Во внутреннем классе нельзя определять статические члены, так как он связан с экземпляром охватывающего класса. (Проверьте, можно ли во внутреннем классе определить статическую константу базового и/или ссылочного типа, – в частности для констант времени компиляции).

  • Объект (инстанс) внутреннего класса может существовать только внутри объекта охватывающего класса, имея прямой доступ ко всем полям и методам охватывающего объекта.

  • Чтобы инстанциировать внутренний класс, надо сперва иметь объект охватывающего класса:

  • OuterClass.InnerClass innerObject = outerObject.new InnerClass();



При создании объекта внутреннего класса в его конструктор автоматически (неявно) передается ссылка на объект охватывающего класса; эта ссылка всегда есть в распоряжении объекта внутреннего класса.

  • При создании объекта внутреннего класса в его конструктор автоматически (неявно) передается ссылка на объект охватывающего класса; эта ссылка всегда есть в распоряжении объекта внутреннего класса.

  • Даже если в конструкторе объекта внутреннего класса нет параметров, компилятор сам обеспечит передачу ссылки на объект охватывающего класса в конструктор.

  • Во внутреннем классе эту ссылку на объект внешнего класса можно получить, записав <имя внешнего класса>.this;



Внутренние классы и восходящее преобразование

  • В сущности, объект внутреннего типа является «оберткой» (wrapper’ом) своего объекта охватывающего класса, ко всем «внутренностям» которого этот wrapper имеет прямой доступ (так как всегда имеет ссылку на него).

  • Это удобно использовать для реализации интерфейса, которую можно абсолютно скрыть от окружающего мира. Приватный внутренний класс позволяет полностью скрыть все детали реализации.

  • Охватывающий класс при этом может играть роль:

    • Хранилища приватных данных;
    • Фабрики реализаций интерфейсов (использующих эти данные и реализованных с применением приватных внутренних классов).
    • (в условия полной изоляции реализации интерфейса компилятор имет значительно больше возможностей оптимизировать код реализации)


Пример использования для реализации интерфейса

  • public interface View2D { void draw(…); } // interface to paint 2D-image into some screen area…

  • public interface View3D { void draw(…); } // interface to paint 3D-image into some screen area…

  • public interface View2DProvider { View2D createView2D(); }

  • public interface View3DProvider { View3D createView3D(); }

  • class ViewableObject implements View2DProvider, View3DProvider {

  • // private data: shape, size, appearance, …

  • private class View2DImpl implements View2D {

  • //… implementation details…

  • public void draw() { /* … */ }

  • }

  • private class View3DImpl implements View3D {

  • // … implementation details…

  • public void draw() { /* … */ }

  • }

  • public View2D createView2D(){ return this.new View2DImpl();}

  • public View3D createView3D(){ return this.new View3DImpl();}

  • }



Использование в callback-механизмах

  • Пример интерфейса для callback-механизма:



Кроме того, имеются две специальные разновидности внутренних классов: - локальные внутренние классы; - анонимные внутренние классы.



Локальный внутренний класс

  • Локальный внутренний класс может быть определен внутри метода, например:

      • public MyInterface getMyInterfaceReference(/* ... параметры метода...*/){
      • class MyInterfaceImpl implements MyInterface {
      • private MyInterfaceImpl(/*... параметры конструктора... */) {
      • }
      • //… прочие члены локального внутреннего класса
      • }
      • return new MyInterfaceImpl (/*…*/);
      • }
  • Вложенный класс теперь не часть класса, а часть метода, и не может быть доступен вне его.

  • Конфликта имен не происходит – имена классов генерируются.



Внутренний класс, локальный в блоке

  • Область определения класса может быть сужена до блока, например:

      • public MyInterface getMyInterfaceReference(/* ... параметры метода...*/){
      • if (isPermission(…)) {
        • class MyInterfaceImpl implements MyInterface {
        • private MyInterfaceImpl(/*... параметры конструктора... */) {
        • }
        • //… прочие члены локального внутреннего класса
        • }
      • return new MyInterfaceImpl (/*…*/);
      • else {
      • return null;
      • }
      • }


Анонимные внутренние классы

  • Анонимные классы встречаются в программах сравнительно часто – когда нужен один-единственный экземпляр локального класса (и этому классу можно не давать имени).



В анонимном классе можно использовать конструктор с параметрами, а вместо интерфейса - базовый класс, например:



Иметь конструктор в анонимном классе нельзя (так как у него нет имени), но можно иметь instance initializer:

  • Иметь конструктор в анонимном классе нельзя (так как у него нет имени), но можно иметь instance initializer:



Анонимные внутренние классы могут наследовать либо класс, либо интерфейс (причем - единственный). При этом нет возможности иметь конструкторы с разными сигнатурами, может быть только единственный инстанс-инициализатор.

  • Анонимные внутренние классы могут наследовать либо класс, либо интерфейс (причем - единственный). При этом нет возможности иметь конструкторы с разными сигнатурами, может быть только единственный инстанс-инициализатор.

  • Вложеннные в инерфейс классы автоматически являются общедоступными статическими вложенными классами ( static и public):

    • public interface MyIntegface {
    • class NestedClass // это public static class (по определению...)
    • {
    • }
    • }


Q&A