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

До сих пор в нашем курсе мы рассматривали так называемые классы верхнего уровня (то есть те классы, которые непосредственно являлись членами пакетов и не были вложены в другие классы). В Java1.1 появились четыре новых типа классов, именуемых «внутренними» классами. Эти четыре типа классов представлены ниже:

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

Вложенный статический класс – это класс, определенный как static член другого класса. Вложенный статический класс очень похож на обычный класс верхнего уровня, за исключением того, что он вложен в другой класс.

В примере 1.1 представлен вспомогательный класс Inner, определенный как статический член окружающего класса Bar.

Пример 1.1. Определение и использование статического вложенного класса.

public class Bar {
  void foo {  
    // создание объекта вложенного статического класса Inner
    new Inner();
  }
  
  static final int CONSTANT = 0;

  // вложенный статический класс
  private static class Inner {
    // поля и методы класса
  }
}

Свойства вложенного класса:

Пример 1.2

public class Outer {
  private int x;
  static class Inner {
    // создаем объект окружающего класса
    Outer o = new Outer();
    // обращаемся к полю x
    o.x;
  }
}

Вложенные классы можно импортировать, используя директиву import:

// импортируем вложенный класс Inner 
import Bar.Inner;

// импортируем все вложенные классы внешнего класса Bar
import Bar.*;

Но импортировать вложенные классы не рекомендуется, потому что эта операция скрывает факт того, что вложенный класс тесно связан с содержащим его классом.

Применение

Основное применение вложенные классы находят в тех случаях, когда необходимо написать вспомогательный класс для другого класса. Таким образом, вложенный класс еще один элегантный способ ограничения области видимости.

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

Вложенный нестатический класс – это класс, который объявлен как нестатический член окружающего класса. Если статический вложенный класс принадлежал всему классу, то нестатический вложенный класс всегда связан  с конкретным экземпляром  окружающего класса.

Пример 2 иллюстрирует определение нестатического вложенного класса.

Пример2. Определение и использование нестатического вложенного класса.

public class Bar {
  // вложенный нестатический класс
  private class Inner {
    // поля и методы класса
  }
}

Свойства вложенного нестатического класса:

 Существует другая возможность создать экземпляр вложенного нестатического класса в любом месте окружающего класса. В нашем примере это выглядит так:

Bar myBar = new Bar(); // создаем объект окружающего класса
myBar.new Inner();     // создаем объект вложенного класса

Для вложенного нестатического класса компилятор определяет искусственное поле в каждом классе, предназначенное для хранения  ссылки на окружающий экземпляр. Каждый конструктор класса-члена получает дополнительный параметр, инициализирующий это поле. Каждый раз при вызове конструктора класса-члена компилятор автоматически передает  ссылку на экземпляр окружающего класса.

вложенный нестатический класс вложенный статический класс
Поля класса Inner
Ссылка на создавший его объект
Поля класса Inner

Локальные классы

Локальный класс – это класс, объявленный в блоке Java кода. Обычно локальный класс определяется в методе, но он также может быть объявлен в инициализаторе экземпляра класса. Поскольку все блоки Java кода находятся внутри определения класса, то все локальные классы вложены в окружающие классы.

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

Пример 3 иллюстрирует определение и использование локального класса Dummy внутри метода i() какого-то класса.

Пример 3. Определение и использование локального класса.

MyInterface myFunction() {
  // определение класса Dummy как локального класса
  class Dummy implements MyInterface {
    // поля и методы класса
  }

  // возвращаем экземпляр класса Dummy, который только что создали
  return Dummy();
}

Свойства локального класса:

Применение

Основное применение локальные классы находят в тех случаях, когда необходимо написать класс, который будет использоваться внутри одного метода. Таким образом, создание локального класса – способ не загромождать пространство имен.

Анонимные классы

Анонимный класс – локальный класс без имени. Анонимный класс определяется и инициализируется в едином выражении с помощью оператора new. Несмотря на то, что определение локального класса в Java – это оператор в блоке, определение анонимного класса представляет собой выражение. Это означает, что его можно записать как часть большого выражения, например метода.

Пример 4. демонстрирует определение и использование анонимного класса.

Пример 4. Определение и использование анонимного класса.

public class Foo {
  // определено множество различных методов
  
  public void foo1() { }
  public void foo2() { }
  …
  public void foo100() { }
}

Хотим создать всего один экземпляр точно такого же класса, как Foo, но с новым методом foo21():

Foo f = new Foo {
    public void foo21() {
      // код метода
    }
  }

То есть мы получили экземпляр класса наследника класса Foo с новым методом foo21().

Свойства анонимного класса:

Применение

Полезной отличительной особенностью локальных и анонимных классов от других «внутренних» классов является то, что первые могут ссылаться на локальные переменные и параметры той области видимости, в которой они определены, объявленные как final. Это доступно локальному классу потому, что компилятор автоматически отдает private поле класса для копии каждой используемой локальной переменной. Кроме того, компилятор добавляет скрытые параметры в каждый конструктор локального класса для инициализации автоматически созданных private полей. Поэтому локальный класс не обращается к локальным переменным, а работает с собственными их копиями.  Эти локальные переменные могут быть только final, что дает гарантию того, что значения внутренних копий локальных переменных не расходятся с настоящими значениями локальных переменных.

Можно заметить, что в нашем примере для достижения результата с таким же успехом можно было просто создать новый класс, отнаследованный от класса Foo и создать объект этого нового класса. Но что будет, если нам захочется создать множество единичных экземпляров  классов, каждый из которых отличается от Foo всего лишь одной функцией.  Согласитесь, в таком случае использование анонимных классов  куда удобнее.

Таким образом, особенность анонимных классов заключается в том, что они позволяют определить короткий класс как раз в том месте, где это необходимо. Кроме того, у анонимных классов лаконичный синтаксис, что уменьшает путаницу в коде.