Верно ли, что каждый внутренний класс требует вмещающего экземпляра?
Термин "внутренний класс" принято понимать как "вложенный класс, который требует включающего экземпляра". Тем не менее, JLS гласит следующее:
[...]
Внутренние классы включают в себя локальные (§14.3), анонимные (§15.9.5) и нестатические классы-члены (§8.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
членами класса, и классы, которые являются членами пакета, называются классами верхнего уровня. Они отличаются от внутренних классов тем, что класс верхнего уровня может напрямую использовать только свои собственные переменные экземпляра.
В то время как в общем использовании верхний уровень считается противоположностью вложенного.
Ответы
Ответ 1
Различия, изложенные в вопросе, имеют прекрасный смысл с точки зрения спецификации:
-
внутренний класс имеет к нему ограничения, которые не имеют никакого отношения к вопросу о включении экземпляров (например, у него могут не быть статические члены);
-
понятие статического вложенного класса в основном касается пространства имен; эти классы можно по праву назвать высокоуровневыми, вместе с тем, что мы обычно принимаем как классы верхнего уровня.
Так получилось, что удаление static
из вложенного объявления класса делает сразу две отдельные вещи:
- он заставляет класс требовать охватывающий экземпляр;
- он делает класс внутренним.
Мы редко думаем о внутренних, как о неизбежных ограничениях; мы фокусируемся только на охватывающей инстанции, которая намного более заметна. Однако, с точки зрения спецификации, ограничения являются жизненно важными.
То, что нам не хватает, - это термин для класса, требующего вмещающего экземпляра. Нет такого термина, который определен JLS, поэтому мы (не подозревая, кажется), захватили связанный, но на самом деле существенно другой, термин означает это.
Ответ 2
Хорошо, не имеет ли анонимный класс закрытый экземпляр в вашем случае? Это ссылка, которая статична, а не экземпляр анонимного класса. Рассмотрим:
class A {
int t() { return 1; }
static A a = new A() { { System.out.println(t()); } };
}
Ответ 3
Нет никакой разницы между статическим внутренним классом и статикой. я не понимаю, почему их следует рассматривать отдельно. Посмотрите на следующий код:
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
}
На самом деле нет никакой разницы между ними. Я бы сказал, что нестатический внутренний класс - это просто синтаксический сахар. Более короткий способ написать общую вещь, не более. Единственное небольшое различие заключается в том, что в нестационарном внутреннем классе ссылка на охватывающий класс назначается перед вызовом родительского конструктора, это может повлиять на некоторую логику, но я не думаю, что это так важно, чтобы рассматривать их отдельно.
P.S. Еще один вопрос по теме, которая может быть интересной.