Вложенные и внутренние классы в Java. Часть 1
В этом уроке мы изучим вложенные и внутренние классы в Java.
Синтаксис языка Java позволяет нам объявлять классы внутри другого класса, такие классы называются внутренними или вложенными.
Синтаксис
Пример:
class OuterClass { ... class NestedClass { ... } }
Вложенные классы делятся на два вида: статические и не статические. Вложенные классы, объявленные как статические называются вложенными статическими (static nested classes). Не статические называются внутренними (inner classes).
class OuterClass { ... static class StaticNestedClass { ... } class InnerClass { ... } }
Вложенные классы — элементы содержащего их класса. Не статические классы имеют доступ к полям содержащего класса, даже если они объявлены как private. Статические — не имеют доступ к членам внешнего класса. Как и другие поля класса, вложенные классы могут быть объявлены как private, public, protected, или package private.
Для чего использовать вложенные классы
Вот некоторые причины использования вложенных классов:
- Это хороший способ группировки классов, которые используются только в одном месте: если класс полезен только для одного другого класса, то логично будет держать их вместе. Вложение таких вспомогательных классов делает код более удобным.
- Инкапсуляция: допустим, есть два класса A и B, классу B требуется доступ к свойству класса A, которое может быть приватным. Вложение класса B в класс A решит эту проблему, более того сам класс B можно скрыть от внешнего использования.
- Улучшение читаемости и обслуживаемости кода: вложение малых классов в более высокоуровневые классы позволяет хранить код там, где он используется.
Вложенные классы
Как статические переменные и методы, вложенный (статический) класс связан с внешнимклассом. И так же как и статические методы не может напрямую обращаться к полям объекта внешнего класса.
Доступ к вложенному классу осуществляется с помощью следующей конструкции:
OuterClass.StaticNestedClass
Синтаксис создания объекта вложенного класса:
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
Внутренние классы
Внутренние (не статические) классы, как переменные и методы связаны с объектом внешнего класса. Внутренние классы так же имеют прямой доступ к полям внешнего класса. Такие классы не могут содержать в себе статические методы и поля. Внутренние классы не могут существовать без экземпляра внешнего. Для создания объекта:
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
Пример
Рассмотрим внутренний класс в действии. В следующем примере, мы создадим массив, заполним целыми числами и выведем только числа с четными индексами в порядке возрастания.
Структура следующей программы:
- Внешний класс
DataStructure
с конструктором для создания и заполнения массива и методом для вывода элементов с четными индексами. - Внутренний класс
EvenIterator
, который реализует интерфейсDataStructureIterator
, который наследует интерфейс Iterator<Integer>. Итераторы используются для доступа к элементам различных структур данных. Они обычно имеют методы для проверки последнего элемента, получения текущего и следующего элементов. - Метод
main
, который создает объектDataStructure
(ds
) и вызывает методprintEven
для вывода элементов с четными индексами.
public class DataStructure { private final static int SIZE = 15; private int[] arrayOfInts = new int[SIZE]; public DataStructure() { for (int i = 0; i < SIZE; i++) { arrayOfInts[i] = i; } } public void printEven() { DataStructureIterator iterator = this.new EvenIterator(); while (iterator.hasNext()) { System.out.print(iterator.next() + " "); } System.out.println(); } interface DataStructureIterator extends java.util.Iterator<Integer> { } private class EvenIterator implements DataStructureIterator { private int nextIndex = 0; public boolean hasNext() { return (nextIndex <= SIZE - 1); } public Integer next() { Integer retValue = Integer.valueOf(arrayOfInts[nextIndex]); nextIndex += 2; return retValue; } } public static void main(String s[]) { DataStructure ds = new DataStructure(); ds.printEven(); } }
Программа выведет:
0 2 4 6 8 10 12 14
Обратите внимание, что класс EvenIterator
напрямую обращается к полю arrayOfInts
класса DataStructure
.
Затенение (shadowing)
Если вы объявляете переменную (будь то параметр или свойство класса) в какой-либо области (например, вложенный класс или область метода) с именем уже занятым в данной области, то такое объявление «затеняет» предыдущее и вы не сможете обращаться напрямую к переменной по её имени. Следующий пример демонстрирует эту ситуацию:
public class ShadowTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) { System.out.println("x = " + x); System.out.println("this.x = " + this.x); System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); } } public static void main(String... args) { ShadowTest st = new ShadowTest(); ShadowTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); } }
Этот код выведет следующее:
x = 23 this.x = 1 ShadowTest.this.x = 0
В этом примере объявлено три переменные с именем x: поле класса ShadowTest
, поле внутреннего класса FirstLevel
, и параметр метода methodInFirstLevel
. Переменная объявленная как параметр функции methodInFirstLevel
затеняет поле внутреннего класса FirstLevel
. Следовательно при использовании переменной x
в методе methodInFirstLevel
, будет использоваться значение переданного параметра. Для обращения к полю класса FirstLevel
, используйте ключевое слово this
:
System.out.println("this.x = " + this.x);
Для использования поля x внешнего класса необходимо использовать конструкцию:
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
На этом всё! В следующей части мы рассмотрим еще два типа внутренних классов: локальные и анонимные классы.
«Статические — не имеют доступ к членам внешнего класса.»
А если члены внешнего класса статические?
Статический внутренний класс (вложеный) видит статические переменные внешнего класса.
class Outer
{
private static int fieldA = 1;
private int fieldB = 2;
public static class Nested
{
public void method(int d)
{
fieldA = d; //работает
//fieldB = d; //не работает
}
}
}