Вложенные классы в Java

1. Введение

Это руководство представляет собой быстрое и по существу введение во вложенные классы на языке Java.

Проще говоря, Java позволяет нам определять классы внутри других классов. Вложенные классы позволяют нам логически группировать классы, которые используются только в одном месте, писать более читаемый и поддерживаемый код и увеличивать инкапсуляцию.

Прежде чем мы начнем, давайте взглянем на несколько типов вложенных классов, доступных в языке:

  • Статические вложенные классы
  • Нестатические вложенные классы
  • Местные классы
  • Анонимные классы

В следующих разделах мы подробно обсудим каждый из них.

2. Статические вложенные классы

Вот несколько моментов, которые следует помнить о статических вложенных классах:

  • Как и статические члены, они принадлежат к их включающему классу, а не к экземпляру класса.
  • Они могут иметь в своем объявлении все типы модификаторов доступа.
  • У них есть доступ только к статическим членам в включающем классе
  • Они могут определять как статические, так и нестатические члены.

Давайте посмотрим, как мы можем объявить статический вложенный класс:

public class Enclosing { private static int x = 1; public static class StaticNested { private void run() { // method implementation } } @Test public void test() { Enclosing.StaticNested nested = new Enclosing.StaticNested(); nested.run(); } }

3. Нестатические вложенные классы

Далее, вот несколько моментов, которые следует помнить о нестатических вложенных классах:

  • Их также называют внутренними классами
  • Они могут иметь в своем объявлении все типы модификаторов доступа.
  • Как и переменные и методы экземпляра, внутренние классы связаны с экземпляром включающего класса.
  • У них есть доступ ко всем членам включающего класса, независимо от того, статические они или нестатические.
  • Они могут определять только нестатические члены

Вот как мы можем объявить внутренний класс:

public class Outer { public class Inner { // ... } }

Если мы объявляем вложенный класс с модификатором static , то это статический член. В противном случае это внутренний класс. Хотя синтаксически разница заключается только в одном ключевом слове (т. Е. Статическом ), семантически существует огромная разница между этими типами вложенных классов. Экземпляры внутреннего класса привязаны к экземплярам класса-оболочки, и поэтому они имеют доступ к своим членам. Мы должны знать об этой проблеме, когда выбираем, делать ли вложенный класс внутренним.

Чтобы создать экземпляр внутреннего класса, мы должны сначала создать его включающий класс.

Посмотрим, как это сделать:

Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();

В следующих подразделах мы собираемся показать некоторые особые типы внутренних классов.

3.1. Местные классы

Локальные классы - это особый тип внутренних классов, в которых класс определяется внутри метода или блока области видимости.

Давайте посмотрим, что нужно помнить об этом типе класса:

  • У них не может быть модификаторов доступа в их объявлении
  • У них есть доступ как к статическим, так и к нестатическим членам во включающем контексте.
  • Они могут определять только члены экземпляра

Вот небольшой пример:

public class NewEnclosing { void run() { class Local { void run() { // method implementation } } Local local = new Local(); local.run(); } @Test public void test() { NewEnclosing newEnclosing = new NewEnclosing(); newEnclosing.run(); } }

3.2. Анонимные классы

Анонимные классы могут использоваться для определения реализации интерфейса или абстрактного класса без необходимости создания многоразовой реализации.

Давайте перечислим несколько моментов, которые следует помнить об анонимных классах:

  • У них не может быть модификаторов доступа в их объявлении
  • У них есть доступ как к статическим, так и к нестатическим членам во включающем контексте.
  • Они могут определять только члены экземпляра
  • Это единственный тип вложенных классов, который не может определять конструкторы или расширять / реализовывать другие классы или интерфейсы.

Чтобы определить анонимный класс, давайте сначала определим простой абстрактный класс:

abstract class SimpleAbstractClass { abstract void run(); }

Теперь давайте посмотрим, как мы можем определить анонимный класс:

public class AnonymousInnerUnitTest { @Test public void whenRunAnonymousClass_thenCorrect() { SimpleAbstractClass simpleAbstractClass = new SimpleAbstractClass() { void run() { // method implementation } }; simpleAbstractClass.run(); } }

Для получения дополнительной информации мы можем найти полезным наш учебник по анонимным классам в Java.

4. Затенение

Объявление членов внутреннего класса затеняет те из включающего класса, если они имеют то же имя.

В этом случае ключевое слово this относится к экземплярам вложенного класса, а на члены внешнего класса можно ссылаться, используя имя внешнего класса.

Давайте посмотрим на быстрый пример:

public class NewOuter { int a = 1; static int b = 2; public class InnerClass { int a = 3; static final int b = 4; public void run() { System.out.println("a = " + a); System.out.println("b = " + b); System.out.println("NewOuterTest.this.a = " + NewOuter.this.a); System.out.println("NewOuterTest.b = " + NewOuter.b); System.out.println("NewOuterTest.this.b = " + NewOuter.this.b); } } @Test public void test() { NewOuter outer = new NewOuter(); NewOuter.InnerClass inner = outer.new InnerClass(); inner.run(); } }

5. Сериализация

Чтобы избежать исключения java.io.NotSerializableException при попытке сериализации вложенного класса, мы должны:

  • Объявить вложенный класс статическим
  • Сделайте как вложенный класс, так и включающий класс реализуемым Serializable

6. Заключение

В этой статье мы увидели, что такое вложенные классы и их различные типы. Мы также рассмотрели, как модификаторы видимости и доступа к полям различаются для этих разных типов.

Как всегда, полную реализацию этого руководства можно найти на GitHub.