Вложенные и внутренние классы в 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; //не работает
}
}
}