Вопрос-ответ

Java inner class and static nested class

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

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

Переведено автоматически
Ответ 1

Из руководства по Java:


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


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

OuterClass.StaticNestedClass

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

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

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

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

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

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

OuterClass outerObject = new 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() { ... } это внутренний класс, определенный в статическом контексте и не имеющий заключающего экземпляра.

Ответ 2

В руководстве по Java говорится:


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


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

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

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


  • organization: sometimes it seems most sensible to sort a class into the namespace of another class, especially when it won't be used in any other context

  • access: nested classes have special access to the variables/fields of their containing classes (precisely which variables/fields depends on the kind of nested class, whether inner or static).

  • convenience: having to create a new file for every new type is bothersome, again, especially when the type will only be used in one context

There are four kinds of nested class in Java. In brief, they are:


  • static class: declared as a static member of another class

  • inner class: declared as an instance member of another class

  • local inner class: declared inside an instance method of another class

  • anonymous inner class: like a local inner class, but written as an expression which returns a one-off object

Let me elaborate in more details.


Static Classes

Static classes are the easiest kind to understand because they have nothing to do with instances of the containing class.

A static class is a class declared as a static member of another class. Just like other static members, such a class is really just a hanger on that uses the containing class as its namespace, e.g. the class Goat declared as a static member of class Rhino in the package pizza is known by the name pizza.Rhino.Goat.

package pizza;

public class Rhino {

...

public static class Goat {
...
}
}

Frankly, static classes are a pretty worthless feature because classes are already divided into namespaces by packages. The only real conceivable reason to create a static class is that such a class has access to its containing class's private static members, but I find this to be a pretty lame justification for the static class feature to exist.


Inner Classes

An inner class is a class declared as a non-static member of another class:

package pizza;

public class Rhino {

public class Goat {
...
}

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

Like with a static class, the inner class is known as qualified by its containing class name, pizza.Rhino.Goat, but inside the containing class, it can be known by its simple name. However, every instance of an inner class is tied to a particular instance of its containing class: above, the Goat created in jerry, is implicitly tied to the Rhino instance this in jerry. Otherwise, we make the associated Rhino instance explicit when we instantiate Goat:

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

(Notice you refer to the inner type as just Goat in the weird new syntax: Java infers the containing type from the rhino part. And, yes new rhino.Goat() would have made more sense to me too.)

So what does this gain us? Well, the inner class instance has access to the instance members of the containing class instance. These enclosing instance members are referred to inside the inner class via just their simple names, not via this (this in the inner class refers to the inner class instance, not the associated containing class instance):

public class Rhino {

private String barry;

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

In the inner class, you can refer to this of the containing class as Rhino.this, and you can use this to refer to its members, e.g. Rhino.this.barry.


Local Inner Classes

A local inner class is a class declared in the body of a method. Such a class is only known within its containing method, so it can only be instantiated and have its members accessed within its containing method. The gain is that a local inner class instance is tied to and can access the final local variables of its containing method. When the instance uses a final local of its containing method, the variable retains the value it held at the time of the instance's creation, even if the variable has gone out of scope (this is effectively Java's crude, limited version of closures).

Because a local inner class is neither the member of a class or package, it is not declared with an access level. (Be clear, however, that its own members have access levels like in a normal class.)

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


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

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

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

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

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

new *InterfaceName*() {*members*}

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

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

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

Ответ 3

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

Сначала разберемся с терминами правильно:


  • Вложенный класс - это класс, который содержится в другом классе на уровне исходного кода.

  • Он статичен, если вы объявляете его с модификатором 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)

Для полноты картины:

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

Ответ 4

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

Обзор

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

Разница

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

Static Nested Class : can't access enclosing class instance and invoke methods on it, so should be used when the nested class doesn't require access to an instance of the enclosing class . A common use of static nested class is to implement a components of the outer object.

Conclusion

So the main difference between the two from a design standpoint is : nonstatic nested class can access instance of the container class, while static can't.

java