«Внутренние» классы
До сих пор в нашем курсе мы рассматривали так называемые классы верхнего уровня (то есть те классы, которые непосредственно являлись членами пакетов и не были вложены в другие классы). В 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 { // поля и методы класса } }
Свойства вложенного класса:
- Вложенный статический класс определен как static, что делает его аналогом статических полей и методов класса. То есть, как и static метод класса, вложенный статический класс не связан ни с одним экземпляром окружающего класса.
- Вложенный статический класс имеет доступ ко всем static членам окружающего класса. И, наоборот, все методы окружающего класса имеют доступ ко всем членам вложенного статического класса. Например, в нашем примере к static полю CONSTANT класса Bar можно обратиться из вложенного статического класса Inner просто по имени.
- Во вложенном статическом классе нельзя по имени обратиться к не-static членам окружающего класса. Но можно создать экземпляр окружающего класса и обратиться теперь уже к его полю, даже если это поле объявлено как private.
Пример 1.2
public class Outer { private int x; static class Inner { // создаем объект окружающего класса Outer o = new Outer(); // обращаемся к полю x o.x; } }
- Поскольку вложенный статический класс сам является членом класса, то при его объявлении так же может быть использован модификатор доступа.
- В коде, расположенном вне окружающего класса на вложенный статический класс можно ссылаться по имени внешнего класса с последующим добавлением имени вложенного класса. В нашем примере к вложенному классу Inner можно обратиться по Bar.Inner.
Вложенные классы можно импортировать, используя директиву import:
// импортируем вложенный класс Inner import Bar.Inner; // импортируем все вложенные классы внешнего класса Bar import Bar.*;
Но импортировать вложенные классы не рекомендуется, потому что эта операция скрывает факт того, что вложенный класс тесно связан с содержащим его классом.
Применение
Основное применение вложенные классы находят в тех случаях, когда необходимо написать вспомогательный класс для другого класса. Таким образом, вложенный класс еще один элегантный способ ограничения области видимости.
Вложенные нестатические классы
Вложенный нестатический класс – это класс, который объявлен как нестатический член окружающего класса. Если статический вложенный класс принадлежал всему классу, то нестатический вложенный класс всегда связан с конкретным экземпляром окружающего класса.
Пример 2 иллюстрирует определение нестатического вложенного класса.
Пример2. Определение и использование нестатического вложенного класса.
public class Bar { // вложенный нестатический класс private class Inner { // поля и методы класса } }
Свойства вложенного нестатического класса:
- Каждый член вложенного нестатического класса связан с экземпляром окружающего класса, а потому создавать экземпляр вложенного нестатического класса можно только в не-static методах окружающего класса. В противном случае мы просто не узнаем, с каким именно экземпляром окружающего класса связан наш объект. В нашем примере только в не-static методах окружающего класса Bar можно написать
new Inner()
.
Существует другая возможность создать экземпляр вложенного нестатического класса в любом месте окружающего класса. В нашем примере это выглядит так:
Bar myBar = new Bar(); // создаем объект окружающего класса myBar.new Inner(); // создаем объект вложенного класса
- Каждый член вложенного нестатического класса связан с экземпляром окружающего класса, это означает, что код вложенного нестатического класса имеет доступ ко всем полям и методам окружающего класса, включая private члены, и наоборот. В нашем примере к любому полю класса Bar можно обратиться из класса Inner так:
имя_поля
,Bar.this.имя_поля
илиBar.super.имя_поля
. - При объявлении вложенного нестатического класса можно использовать модификатор доступа. Если ни один из модификаторов доступа не указан, то, по умолчанию, применяется пакетная видимость.
- Вложенный нестатический класс не должен иметь static полей, методов и классов, исключение составляют константы, объявленные как static final. Статические поля и методы являются конструкциями верхнего уровня, которые не связаны с конкретными объектами, в то время как каждый вложенный нестатический класс связан с экземпляром окружающего класса. Определение static члена в классе-члене низкого уровня вносит путаницу и является плохим стилем программирования, поэтому следует определять все статические члены в классе верхнего уровня или статическом классе-члене.
- Еще раз подчеркнем, отличие вложенного нестатического класса от вложенного статического класса состоит в том, что первый из них связан с конкретным экземпляром окружающего класс, а второй нет.
Для вложенного нестатического класса компилятор определяет искусственное поле в каждом классе, предназначенное для хранения ссылки на окружающий экземпляр. Каждый конструктор класса-члена получает дополнительный параметр, инициализирующий это поле. Каждый раз при вызове конструктора класса-члена компилятор автоматически передает ссылку на экземпляр окружающего класса.
вложенный нестатический класс | вложенный статический класс | |||
|
|
Локальные классы
Локальный класс – это класс, объявленный в блоке Java кода. Обычно локальный класс определяется в методе, но он также может быть объявлен в инициализаторе экземпляра класса. Поскольку все блоки Java кода находятся внутри определения класса, то все локальные классы вложены в окружающие классы.
Определяющей характеристикой локального класса является вложенность для блока кода. Как и локальная переменная, локальный класс действителен только в области видимости окружающего блока.
Пример 3 иллюстрирует определение и использование локального класса Dummy внутри метода i() какого-то класса.
Пример 3. Определение и использование локального класса.
MyInterface myFunction() { // определение класса Dummy как локального класса class Dummy implements MyInterface { // поля и методы класса } // возвращаем экземпляр класса Dummy, который только что создали return Dummy(); }
Свойства локального класса:
- Подобно вложенным нестатическим классам, локальные классы связаны с окружающим экземпляром и имеют доступ ко всем членам, включая private члены окружающего класса.
- Локальный класс нельзя объявить с каким-либо модификатором доступа. Эти модификаторы используются только для членов класса; они не доступны для объявления локальных переменных или классов.
- Как и нестатические вложенные классы, и по тем же причинам, локальные классы не могут иметь static поля, исключение составляют константы, объявленные как static final.
Применение
Основное применение локальные классы находят в тех случаях, когда необходимо написать класс, который будет использоваться внутри одного метода. Таким образом, создание локального класса – способ не загромождать пространство имен.
Анонимные классы
Анонимный класс – локальный класс без имени. Анонимный класс определяется и инициализируется в едином выражении с помощью оператора 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().
Свойства анонимного класса:
- Так как анонимные классы представляют собой один из типов локальных классов, то они также как и локальные классы не могут иметь static поля и методы, кроме констант - static final. Также как и локальные классы, анонимные классы быть записаны с модификатором доступа.
- Заметим, что, поскольку у анонимного класса нет имени, то невозможно определить конструктор анонимного класса.
Применение
Полезной отличительной особенностью локальных и анонимных классов от других «внутренних» классов является то, что первые могут ссылаться на локальные переменные и параметры той области видимости, в которой они определены, объявленные как final. Это доступно локальному классу потому, что компилятор автоматически отдает private поле класса для копии каждой используемой локальной переменной. Кроме того, компилятор добавляет скрытые параметры в каждый конструктор локального класса для инициализации автоматически созданных private полей. Поэтому локальный класс не обращается к локальным переменным, а работает с собственными их копиями. Эти локальные переменные могут быть только final, что дает гарантию того, что значения внутренних копий локальных переменных не расходятся с настоящими значениями локальных переменных.
Можно заметить, что в нашем примере для достижения результата с таким же успехом можно было просто создать новый класс, отнаследованный от класса Foo и создать объект этого нового класса. Но что будет, если нам захочется создать множество единичных экземпляров классов, каждый из которых отличается от Foo всего лишь одной функцией. Согласитесь, в таком случае использование анонимных классов куда удобнее.
Таким образом, особенность анонимных классов заключается в том, что они позволяют определить короткий класс как раз в том месте, где это необходимо. Кроме того, у анонимных классов лаконичный синтаксис, что уменьшает путаницу в коде.