Вложенные и внутренние классы в Java. Часть 1

12-10-21 Java OOP 2

В этом уроке мы изучим вложенные и внутренние классы в 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);

На этом всё! В следующей части мы рассмотрим еще два типа внутренних классов: локальные и анонимные классы.

Хочешь получать статьи на почту?

Подпишись на обновления!
* Ваш email не будет разглашен/продан. Вы сможете отписаться в любое время.

2 Комментария

  1. Vetal says:

    «Статические — не имеют доступ к членам внешнего класса.»

    А если члены внешнего класса статические?

    1. Анна says:

      Статический внутренний класс (вложеный) видит статические переменные внешнего класса.
      class Outer
      {
      private static int fieldA = 1;
      private int fieldB = 2;

      public static class Nested
      {
      public void method(int d)
      {
      fieldA = d; //работает
      //fieldB = d; //не работает
      }
      }

      }

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *