Вложенные (nested) классы и интерфейсы
Вложенный класс задаётся во внешнем классе так:
class ИмяВнешнегоКласса{
тело внешнего класса
static class ИмяВложенногоКласса{
тело вложенного класса
}
продолжение тела внешнего класса
}
Экземпляры вложенного класса, а также методы класса и поля класса получают в имени квалификатор – имя класса верхнего уровня.
Например, доступ к полю идёт как
ИмяВнешнегоКласса.ИмяВложенногоКласса.имяПоля,
а обращение к методу класса – как
ИмяВнешнегоКласса.ИмяВложенногоКласса.имяМетода(список параметров).
Пусть у нас имя внешнего класса C1, а вложенного C_nested. Тогда создание экземпляра вложенного класса может идти, например так:
C1.C_nested obj=new C1.C_nested();
Особенностью использования вложенных классов является то, что во внешнем классе могут быть поля, имеющие тип вложенного класса. При этом для данного случая квалификацию именем внешнего класса использовать не надо. Отметим, что в этом случае применяется то же правило, что и при доступе к обычным полям или методам, заданным в классе.
Пример:
class C1{
private C_nested obj1;
static class C_nested {
тело вложенного класса
}
C_nested getNested(){
return obj1;
}
}
При компиляции для вложенных классов создаются самостоятельные классы .class, имеющие имя имяВнешнегоКласса$имяВложенногоКласса.class . Точно такое же имя выдаётся в методах объектВложенногоКласса.toString() или объектВложенногоКласса.getClass().getName(). А вот объектВложенногоКласса.getClass().getCanonicalName() возвращает имя вложенного класса через точку.
Задание вложенного интерфейса аналогично заданию вложенного класса:
class ИмяВнешнегоКласса{
тело внешнего класса
interface ИмяВложенногоИнтерфейса{
объявление констант и заголовков методов
}
продолжение тела внешнего класса
}
Вложенные интерфейсы считаются имеющими модификатор static.
Реализовывать вложенный интерфейс можно в постороннем классе – при этом имя интерфейса квалифицируется именем внешнего класса. Если же реализация идёт в самом внешнем классе, квалификация именем этого класса не требуется.
Как правило, необходимость во вложенных классах возникает только в тех случаях, когда внешний класс служит заменой модуля процедурного языка программирования. В этом случае обычные классы приходится вкладывать во внешний класс, и они становятся вложенными.
Внутренние (inner) классы
Внутренний класс задаётся так же, как вложенный, но только без модификатора static перед именем этого класса:
class ИмяВнешнегоКласса{
тело внешнего класса
class ИмяВнутреннегоКласса{
тело внутреннего класса
}
продолжение тела внешнего класса
}
Для внутренних классов экземпляры создаются через имя объекта внешнего класса, что принципиально отличает их от обычных и вложенных классов.
Синтаксис таков:
Сначала идёт создание экземпляра внешнего класса:
ИмяВнешнегоКласса имяОбъекта =new ИмяВнешнегоКласса(параметры);
Затем создаётся нужное число экземпляров внутреннего класса:
ИмяВнешнегоКласса.ИмяВнутреннегоКласса имя1 =
имяОбъекта.new ИмяВнутреннегоКласса(параметры);
ИмяВнешнегоКласса.ИмяВнутреннегоКласса имя2 =
имяОбъекта.new ИмяВнутреннегоКласса(параметры);
и так далее.
Достаточно часто из внутреннего класса необходимо обратиться к объекту внешнего класса. Такое обращение идёт через имя внешнего класса и ссылку this на текущий объект:
ИмяВнешнегоКласса.this
- это ссылка на внешний объект (его естественно назвать родительским объектом). А доступ к полю или методу внешнего объекта в этом случае, естественно, идёт так:
ИмяВнешнегоКласса.this.имяПоля
ИмяВнешнегоКласса.this.имяМетода(список параметров).
К сожалению, в Java, в отличие от языка JavaScript, нет зарезервированного слова parent для обращения к родительскому объекту. Будем надеяться, что в дальнейшем в java будет введён этот гораздо более читаемый и удобный способ обращения к родителю.
Пример работы с внутренними классами:
package java_gui_example;
public class OuterClass {
int a=5;
public OuterClass() {
}
public class InnerClass{
int x=1,y=1;
public class InnerClass2 {
int z=0;
InnerClass2(){
System.out.println("InnerClass2 object created");
};
void printParentClassNames(){
System.out.println("InnerClass.this.x="+InnerClass.this.x);
System.out.println("OuterClass.this.a="+OuterClass.this.a);
}
}
}
InnerClass inner1;
InnerClass.InnerClass2 inner2;
public void createInner() {
inner1=this.new InnerClass();
inner2=inner1.new InnerClass2();
System.out.println("inner1 name="+inner1.getClass().getName());
System.out.println("inner1 canonical name="+
inner1.getClass().getCanonicalName());
}
}
Если в приложении задать переменную типа OuterClass и создать соответствующий объект
OuterClass outer1=new OuterClass();
то после этого можно создать объекты внутренних классов:
outer1.createInner();
Доступ к внешним объектам иллюстрируется при вызове метода
outer1.inner2.printParentClassNames();
Заметим, что при создании внутреннего класса в приложении, а не в реализации класса OuterClass, вместо
InnerClass inner1=this.new InnerClass();
и
InnerClass.InnerClass2 inner2= inner1.new InnerClass2();
придётся написать
OuterClass.InnerClass inner3=outer1.new InnerClass();
OuterClass.InnerClass.InnerClass2 inner4=inner3.new InnerClass2();
Необходимость во внутренних классах обычно возникает в случаях, когда внешний класс описывает сложную систему, состоящую из частей, каждая из которых, в свою очередь, является системой, очень тесно связанной с внешней. Причём может существовать несколько экземпляров внешних систем. Для такого варианта агрегации идеология внутренних классов подходит очень хорошо.
Локальные (local) классы
Никаких особенностей в применении локальных классов нет, за исключением того, что область существования их и их экземпляров ограничена тем блоком, в котором они заданы. Пример использования локального класса:
class LocalClass1 {
public LocalClass1(){
System.out.println("LocalClass1 object created");
}
};
LocalClass1 local1=new LocalClass1();
Этот код можно вставить в любой метод. Например, в обработчик события нажатия на кнопку. Конечно, данный пример чисто иллюстративный.