Внутренний класс Java и статический вложенный класс - PullRequest
1626 голосов
/ 16 сентября 2008

В чем основное различие между внутренним классом и статическим вложенным классом в Java? Играет ли дизайн / реализация роль в выборе одного из них?

Ответы [ 25 ]

1592 голосов
/ 16 сентября 2008

Из Java Tutorial :

Вложенные классы делятся на две категории: статические и нестатические. Вложенные классы, которые объявлены статическими, просто называются статическими вложенными классами. Нестатические вложенные классы называются внутренними классами.

Доступ к статическим вложенным классам осуществляется с использованием имени включающего класса:

OuterClass.StaticNestedClass

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

OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();

Объекты, которые являются экземплярами внутреннего класса, существуют в экземпляре внешнего класса. Рассмотрим следующие классы:

class OuterClass {
    ...
    class InnerClass {
        ...
    }
}

Экземпляр InnerClass может существовать только внутри экземпляра OuterClass и имеет прямой доступ к методам и полям его включающего экземпляра.

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

OuterClass.InnerClass innerObject = outerObject.new InnerClass();

см .: Учебник Java - Вложенные классы

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

class A {
  int t() { return 1; }
  static A a =  new A() { int t() { return 2; } };
}

Здесь new A() { ... } - это внутренний класс , определенный в статическом контексте и не имеющий включающего экземпляра.

568 голосов
/ 16 сентября 2008

В руководстве по Java написано :

Терминология: вложенные классы делится на две категории: статические и не статично. Вложенные классы, которые объявлены статическими просто называются статические вложенные классы. Нестатические вложенные классы называются внутренними классы.

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

Классы могут быть вложенными до бесконечности , например класс A может содержать класс B, который содержит класс C, который содержит класс D и т. д. Тем не менее, более одного уровня вложенности классов встречается редко, так как обычно это плохой дизайн.

Существует три причины, по которым вы можете создать вложенный класс:

  • организация: иногда кажется наиболее разумным отсортировать класс в пространство имен другого класса, особенно когда он не будет использоваться ни в каком другом контексте
  • доступ: вложенные классы имеют специальный доступ к переменным / полям содержащихся в них классов (какие именно переменные / поля зависят от типа вложенного класса, внутреннего или статического).
  • удобство: необходимость создавать новый файл для каждого нового типа утомительна, опять же, особенно когда тип будет использоваться только в одном контексте

В Java существует четырех видов вложенных классов . Вкратце, это:

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

Позвольте мне уточнить детали.


Статические классы

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

Статический класс - это класс, объявленный как статический член другого класса. Как и другие статические члены, такой класс на самом деле является просто вешалкой, который использует содержащий класс в качестве своего пространства имен, например, класс Goat , объявленный как статический член класса Rhino в упаковке pizza известен под названием pizza.Rhino.Goat .

package pizza;

public class Rhino {

    ...

    public static class Goat {
        ...
    }
}

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


Внутренние классы

Внутренний класс - это класс, объявленный как нестатический член другого класса:

package pizza;

public class Rhino {

    public class Goat {
        ...
    }

    private void jerry() {
        Goat g = new Goat();
    }
}

Как и в случае статического класса, внутренний класс известен как квалифицированный по имени содержащего класса, pizza.Rhino.Goat , но внутри содержащего класса он может быть известен по его простому имени. Однако каждый экземпляр внутреннего класса привязан к конкретному экземпляру содержащего его класса: выше, Goat , созданный в jerry , неявно привязан к Rhino экземпляр это в Джерри . В противном случае мы делаем явным связанный экземпляр Rhino при создании экземпляра Goat :

Rhino rhino = new Rhino();
Rhino.Goat goat = rhino.new Goat();

(Обратите внимание, что вы называете внутренний тип просто Goat в странном синтаксисе new : Java выводит содержащий тип из части rhino . И, да новый носорог. Goat () имел бы для меня больше смысла.)

Так что это нам дает? Ну, внутренний экземпляр класса имеет доступ к членам экземпляра содержащего экземпляра класса. Эти входящие в состав экземпляры-члены упоминаются внутри внутреннего класса через , только их простые имена, а не через this ( this во внутреннем классе ссылается на внутренний экземпляр класса, а не на связанный с ним экземпляр класса):

public class Rhino {

    private String barry;

    public class Goat {
        public void colin() {
            System.out.println(barry);
        }
    }
}

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


Местные внутренние классы

Локальный внутренний класс - это класс, объявленный в теле метода. Такой класс известен только в пределах его содержащего метода, поэтому он может быть создан только для экземпляра и получить доступ к его членам в пределах содержащего его метода. Преимущество заключается в том, что локальный экземпляр внутреннего класса привязан и может получить доступ к последним локальным переменным его содержащего метода. Когда экземпляр использует последний локальный элемент своего содержащего метода, переменная сохраняет значение, которое оно содержало во время создания экземпляра, даже если переменная вышла из области видимости (это фактически грубая ограниченная версия замыканий Java).

Поскольку локальный внутренний класс не является ни членом класса, ни пакета, он не объявляется с уровнем доступа. (Однако, имейте в виду, что его собственные члены имеют уровни доступа, как в обычном классе.)

Если в методе экземпляра объявлен локальный внутренний класс, создание экземпляра внутреннего класса привязывается к экземпляру, содержащемуся в содержащем методе this во время создания экземпляра, и поэтому содержащий его члены экземпляра класса доступны как в экземпляре внутреннего класса. Локальный внутренний класс создается просто через его имя, например, локальный внутренний класс Cat создается как new Cat () , а не new this.Cat (), как и следовало ожидать.


Анонимные внутренние классы

Анонимный внутренний класс - это синтаксически удобный способ написания локального внутреннего класса. Чаще всего локальный внутренний класс создается не более одного раза при каждом запуске содержащего его метода. Было бы хорошо, если бы мы могли объединить определение локального внутреннего класса и его единственное создание в одну удобную синтаксическую форму, и было бы также хорошо, если бы нам не приходилось придумывать имя для этого класса (тем меньше бесполезного имена ваш код содержит, тем лучше). Анонимный внутренний класс допускает обе эти вещи:

new *ParentClassName*(*constructorArgs*) {*members*}

Это выражение возвращает новый экземпляр безымянного класса, который расширяет ParentClassName . Вы не можете предоставить свой собственный конструктор; скорее, неявно предоставляется один, который просто вызывает супер-конструктор, поэтому предоставленные аргументы должны соответствовать супер-конструктору. (Если родительский элемент содержит несколько конструкторов, то называется «самый простой», «самый простой», что определяется довольно сложным набором правил, которые не стоит беспокоиться, чтобы изучать его подробно - просто обратите внимание на то, что говорят вам NetBeans или Eclipse.) 1160 *

В качестве альтернативы вы можете указать интерфейс для реализации:

new *InterfaceName*() {*members*}

Такое объявление создает новый экземпляр безымянного класса, который расширяет Object и реализует InterfaceName . Опять же, вы не можете предоставить свой собственный конструктор; в этом случае Java неявно предоставляет конструктор без аргументов, бездействия (поэтому в этом случае аргументы конструктора никогда не будут).

Даже если вы не можете присвоить анонимному внутреннему классу конструктор, вы все равно можете выполнить любую настройку, которую хотите, используя блок инициализатора (блок {}, помещенный вне любого метода).

BeПонятно, что анонимный внутренний класс - это просто менее гибкий способ создания локального внутреннего класса с одним экземпляром. Если вам нужен локальный внутренний класс, который реализует несколько интерфейсов или который реализует интерфейсы, расширяя какой-то класс, отличный от Object , или который указывает свой собственный конструктор, вы застряли, создав обычный локальный внутренний класс с именем.

135 голосов
/ 16 сентября 2008

Я не думаю, что реальная разница стала ясна в ответах выше.

Сначала правильно сформулируйте условия:

  • Вложенный класс - это класс, который содержится в другом классе на уровне исходного кода.
  • Это статично, если вы объявляете это с модификатором static .
  • Нестатический вложенный класс называется внутренним классом. (Я остаюсь с нестатическим вложенным классом.)

Ответ Мартина пока верен. Однако актуальный вопрос: какова цель объявления вложенного класса статическим или нет?

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

Нестатические вложенные классы - это разные звери. Подобно анонимным внутренним классам, такие вложенные классы на самом деле являются замыканиями. Это означает, что они фиксируют окружающий их объем и включающий их экземпляр и делают его доступным. Возможно, пример прояснит это. Посмотрите эту заглушку Контейнера:

public class Container {
    public class Item{
        Object data;
        public Container getContainer(){
            return Container.this;
        }
        public Item(Object data) {
            super();
            this.data = data;
        }

    }

    public static Item create(Object data){
        // does not compile since no instance of Container is available
        return new Item(data);
    }
    public Item createSubItem(Object data){
        // compiles, since 'this' Container is available
        return new Item(data);
    }
}

В этом случае вы хотите иметь ссылку от дочернего элемента на родительский контейнер. Используя нестатический вложенный класс, это работает без какой-либо работы. Вы можете получить доступ к включающему экземпляру Контейнера с синтаксисом Container.this.

Более жесткие объяснения следующие:

Если вы посмотрите на байт-коды Java, которые генерирует компилятор для (нестатического) вложенного класса, это может стать еще яснее:

// class version 49.0 (49)
// access flags 33
public class Container$Item {

  // compiled from: Container.java
  // access flags 1
  public INNERCLASS Container$Item Container Item

  // access flags 0
  Object data

  // access flags 4112
  final Container this$0

  // access flags 1
  public getContainer() : Container
   L0
    LINENUMBER 7 L0
    ALOAD 0: this
    GETFIELD Container$Item.this$0 : Container
    ARETURN
   L1
    LOCALVARIABLE this Container$Item L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 1
  public <init>(Container,Object) : void
   L0
    LINENUMBER 12 L0
    ALOAD 0: this
    ALOAD 1
    PUTFIELD Container$Item.this$0 : Container
   L1
    LINENUMBER 10 L1
    ALOAD 0: this
    INVOKESPECIAL Object.<init>() : void
   L2
    LINENUMBER 11 L2
    ALOAD 0: this
    ALOAD 2: data
    PUTFIELD Container$Item.data : Object
    RETURN
   L3
    LOCALVARIABLE this Container$Item L0 L3 0
    LOCALVARIABLE data Object L0 L3 2
    MAXSTACK = 2
    MAXLOCALS = 3
}

Как видите, компилятор создает скрытое поле Container this$0. Это устанавливается в конструкторе, который имеет дополнительный параметр типа Container для указания включающего экземпляра. Вы не можете видеть этот параметр в исходном коде, но компилятор неявно генерирует его для вложенного класса.

Пример Мартина

OuterClass.InnerClass innerObject = outerObject.new InnerClass();

будет скомпилировано для вызова чего-то вроде (в байтовых кодах)

new InnerClass(outerObject)

Ради полноты:

Анонимный класс является прекрасным примером нестатического вложенного класса, с которым просто не связано имя, и на который нельзя ссылаться позже.

90 голосов
/ 27 мая 2012

Я думаю, что ни один из приведенных выше ответов не объясняет вам реальной разницы между вложенным классом и статическим вложенным классом с точки зрения разработки приложения:

OverView

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

Разница

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

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

Заключение

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

32 голосов
/ 18 октября 2013

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

Вложенные классы - это классы, определенные внутри тела другого включающего класса. Они бывают двух типов - статические и нестатические.

Они рассматриваются как члены окружающего класса, поэтому вы можете указать любой из четырех спецификаторов доступа - private, package, protected, public. У нас нет такой роскоши с классами высшего уровня, которые могут быть объявлены только public или пакетом private.

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

public class OuterClass {
    public static class Inner1 {
    }
    public class Inner2 {
    }
}

Inner1 - наш статический внутренний класс, а Inner2 - наш внутренний класс, который не является статическим. Основное различие между ними заключается в том, что вы не можете создать экземпляр Inner2 без Outer, где вы можете создать объект Inner1 независимо.

Когда бы вы использовали Inner class?

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

Для создания экземпляра внутреннего класса вам необходимо создать экземпляр вашего внешнего класса.

OuterClass outer = new OuterClass();
OuterClass.Inner2 inner = outer.new Inner2();

или

OuterClass.Inner2 inner = new OuterClass().new Inner2();

Когда бы вы использовали статический класс Inner?

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

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

OuterClass.Inner1 nestedObject = new OuterClass.Inner1();

Преимущество статического вложенного класса в том, что ему не нужен объект содержащего класса / топ-класса для работы. Это может помочь вам уменьшить количество объектов, создаваемых вашим приложением во время выполнения.

26 голосов
/ 16 декабря 2010

Я думаю, что обычно соблюдается следующее правило:

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

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

  • Классы верхнего уровня и статический вложенный класс семантически одинаковы, за исключением того, что в случае статического вложенного класса он может делать статическую ссылку на частные статические поля / методы своего класса Outer [parent] и наоборот.

  • Внутренние классы имеют доступ к переменным экземпляра включающего экземпляра класса Outer [parent]. Однако не все внутренние классы имеют вложенные экземпляры, например, внутренние классы в статических контекстах, как анонимный класс, используемый в статическом блоке инициализатора, не имеют.

  • Анонимный класс по умолчанию расширяет родительский класс или реализует родительский интерфейс, и больше нет условий для расширения какого-либо другого класса или реализации каких-либо дополнительных интерфейсов. Итак,

    • new YourClass(){}; означает class [Anonymous] extends YourClass {}
    • new YourInterface(){}; означает class [Anonymous] implements YourInterface {}

Я чувствую, что главный вопрос, который остается открытым, какой и когда использовать? Ну, это в основном зависит от того, с каким сценарием вы имеете дело, но чтение ответа, данного @jrudolph, может помочь вам принять какое-то решение.

26 голосов
/ 31 декабря 2015

Вот ключевые различия и сходства между внутренним классом Java и статическим вложенным классом.

Надеюсь, это поможет!

Внутренний класс

  • Может получить доступ к внешнему классу как к экземпляру, так и к статическим методам и полям
  • Связан с экземпляром включающего класса , поэтому для его создания сначала необходим экземпляр внешнего класса (примечание new ключевое место):

    Outerclass.InnerClass innerObject = outerObject.new Innerclass();
    
  • Невозможно определить какие-либо статические элементы само по себе

  • Не может иметь Класс или Интерфейс Декларация

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

  • Невозможно получить доступ внешний класс экземпляр методы или поля

  • Не связан ни с одним экземпляром включающего класса Итак, чтобы создать его экземпляр:

    OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
    

Сходство

  • Оба Внутренние классы могут получить доступ даже к закрытым полям и методам из внешний класс
  • Также Внешний класс имеет доступ к закрытым полям и методам из внутренних классов
  • Оба класса могут иметь модификатор частного, защищенного или открытого доступа

Зачем использовать вложенные классы?

Согласно документации Oracle, существует несколько причин ( полная документация ):

  • Это способ логической группировки классов, которые используются только в одном месте: Если класс полезен только для одного другого класса, то логично встраивать его в этот класс держите их вместе. Вложение таких «вспомогательных классов» делает их пакет более упорядоченным.

  • Увеличивает инкапсуляцию: Рассмотрим два класса верхнего уровня, A и B, где B необходим доступ к членам A, которые в противном случае были бы объявлены закрытыми. Скрывая класс B в классе A, члены A могут быть объявлены частными, и B может получить к ним доступ. Кроме того, сам B может быть скрыт от внешнего мира.

  • Это может привести к более удобочитаемому и поддерживаемому коду: Вложение небольших классов в классы верхнего уровня помещает код ближе к месту его использования.

15 голосов
/ 04 апреля 2010

Вложенный класс: класс внутри класса

Типы:

  1. Статический вложенный класс
  2. Нестатический вложенный класс [Внутренний класс]

Разница:

Нестатический вложенный класс [Внутренний класс]

В нестатическом вложенном классе объект внутреннего класса существует внутри объекта внешнего класса. Так что член данных внешнего класса доступен для внутреннего класса. Таким образом, чтобы создать объект внутреннего класса, мы должны сначала создать объект внешнего класса.

outerclass outerobject=new outerobject();
outerclass.innerclass innerobjcet=outerobject.new innerclass(); 

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

В статическом вложенном классе объект внутреннего класса не нуждается в объекте внешнего класса, поскольку слово «статический» указывает на отсутствие необходимости создания объекта.

class outerclass A {
    static class nestedclass B {
        static int x = 10;
    }
}

Если вы хотите получить доступ к x, напишите следующий внутренний метод

  outerclass.nestedclass.x;  i.e. System.out.prinltn( outerclass.nestedclass.x);
11 голосов
/ 26 марта 2010

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

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

Рассмотрим этот пример:

public class C0 {

    static C0 instance = null;

    // Uncomment the following line and a null pointer exception will be
    // generated before anything gets printed.
    //public static final String outerItem = instance.makeString(98.6);

    public C0() {
        instance = this;
    }

    public String makeString(int i) {
        return ((new Integer(i)).toString());
    }

    public String makeString(double d) {
        return ((new Double(d)).toString());
    }

    public static final class nested {
        public static final String innerItem = instance.makeString(42);
    }

    static public void main(String[] argv) {
        System.out.println("start");
        // Comment out this line and a null pointer exception will be
        // generated after "start" prints and before the following
        // try/catch block even gets entered.
        new C0();
        try {
            System.out.println("retrieve item: " + nested.innerItem);
        }
        catch (Exception e) {
            System.out.println("failed to retrieve item: " + e.toString());
        }
        System.out.println("finish");
    }
}

Несмотря на то, что 'nested' и 'innerItem' оба объявлены как 'static final'. Настройки из nested.innerItem не происходит до тех пор, пока не будет создан экземпляр класса (или, по крайней мере, только после первой ссылки на вложенный статический элемент), как вы можете убедиться сами комментируя и раскомментируя строки, на которые я ссылаюсь, выше. То же самое не держит true для «externalItem».

По крайней мере, это то, что я вижу в Java 6.0.

11 голосов
/ 16 сентября 2008

Экземпляр внутреннего класса создается при создании экземпляра внешнего класса. Поэтому члены и методы внутреннего класса имеют доступ к членам и методам экземпляра (объекта) внешнего класса. Когда экземпляр внешнего класса выходит из области видимости, также экземпляры внутреннего класса перестают существовать.

Статический вложенный класс не имеет конкретного экземпляра. Он просто загружается при первом использовании (как статические методы). Это полностью независимая сущность, чьи методы и переменные не имеют доступа к экземплярам внешнего класса.

Статические вложенные классы не связаны с внешним объектом, они работают быстрее и не занимают кучи / стека, потому что создавать экземпляр такого класса не нужно. Поэтому практическое правило состоит в том, чтобы попытаться определить статический вложенный класс с максимально ограниченной областью действия (private> = class> = protected> = public), а затем преобразовать его во внутренний класс (удалив «статический» идентификатор) и ослабить объем, если это действительно необходимо.

...