Правда ли, что каждому внутреннему классу требуется включающий экземпляр?
Термин " внутренний класс" принято считать "вложенным классом, который требует включающего экземпляра". Тем не менее, JLS гласит следующее:
8.1.3. Внутренние классы и вложенные экземпляры
[...]
Внутренние классы включают в себя локальные (§14.3), анонимные (§15.9.5) и нестатические классы-члены (§8.5).
[...]
Экземпляр внутреннего класса, объявление которого происходит в статическом контексте, не имеет лексически заключенных экземпляров.
Также,
15.9.5. Объявления анонимных классов
[...]
Анонимный класс всегда является внутренним классом (§8.1.3); это никогда
static
(§8.1.1, §8.5.1).
И хорошо известно, что анонимный класс может быть объявлен в статическом контексте:
class A {
int t() { return 1; }
static A a = new A() { int t() { return 2; } };
}
Чтобы описать это остро,
new A() {}
является вложенным классом без включающего экземпляра, определенного в статическом контексте, но это не статический вложенный класс - это внутренний класс.
Все ли мы приписываем неподходящее значение этим терминам в повседневном использовании?
В качестве связанной точки интереса этот исторический документ спецификации определяет термин верхний уровень как противоположность внутреннего:
Классы, которые
static
члены класса и классы, которые являются членами пакета, называются классами верхнего уровня. Они отличаются от внутренних классов тем, что класс верхнего уровня может напрямую использовать только свои собственные переменные экземпляра.
В то время как в общем использовании верхний уровень считается противоположностью вложенного.
3 ответа
Различия, изложенные в этом вопросе, имеют смысл с точки зрения спецификации:
к внутреннему классу применяются ограничения, которые не имеют ничего общего с вопросом о включении экземпляров (например, он может не иметь статических членов);
концепция статического вложенного класса в основном сводится к пространству имен; эти классы могут по праву называться верхними уровнями вместе с тем, что мы обычно считаем классами верхнего уровня.
Так уж получилось, что удаление static
из объявления вложенного класса делает две разные вещи одновременно:
- это заставляет класс требовать включающего экземпляра;
- это делает класс внутренним.
Мы редко думаем о внутреннем как о сопутствующих ограничениях; мы концентрируемся только на проблематике окружающего экземпляра, которая гораздо более заметна. Однако, с точки зрения спецификации, ограничения являются жизненно важной задачей.
Чего нам не хватает, так это термина для класса, требующего включающего экземпляра. Такого термина, определенного JLS, не существует, поэтому мы (кажется, не подозревая) похитили связанный, но на самом деле принципиально другой термин, обозначающий это.
Ну, разве анонимный класс не имеет включающего экземпляра в вашем случае? Это статическая ссылка, а не экземпляр анонимного класса. Рассматривать:
class A {
int t() { return 1; }
static A a = new A() { { System.out.println(t()); } };
}
Нет разницы между статическим внутренним классом и статическим. Я не понимаю, почему они должны рассматриваться отдельно. Посмотрите на следующий код:
public class Outer {
public static class StaticInner{
final Outer parent;
public StaticInner(Outer parent) {
this.parent = parent;
}
};
public class Inner{}
public static void main(String[] args) {
new StaticInner(new Outer());
new Outer().new Inner();
}
}
А потом в StaticInner
а также Inner
Классы байт-код:
public class so.Outer$Inner extends java.lang.Object{
final so.Outer this$0;
public so.Outer$Inner(so.Outer);
Code:
0: aload_0
1: aload_1
2: putfield #1; //Field this$0:Lso/Outer;
5: aload_0
6: invokespecial #2; //Method java/lang/Object."<init>":()V
9: return
}
public class so.Outer$StaticInner extends java.lang.Object{
final so.Outer parent;
public so.Outer$StaticInner(so.Outer);
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: aload_0
5: aload_1
6: putfield #2; //Field parent:Lso/Outer;
9: return
}
На самом деле нет никакой разницы между ними вообще. Я бы сказал, что нестатический внутренний класс - это просто синтаксический сахар. Более короткий способ написать обычную вещь, не более. Единственное небольшое отличие состоит в том, что в нестатическом внутреннем классе ссылка на включающий класс назначается перед вызовом родительского конструктора, это может повлиять на некоторую логику, но я не думаю, что очень важно рассматривать их отдельно.
PS Еще один вопрос по смежной теме, который может быть интересным.