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

Термин "внутренний класс" принято понимать как "вложенный класс, который требует включающего экземпляра". Тем не менее, 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 членами класса, и классы, которые являются членами пакета, называются классами верхнего уровня. Они отличаются от внутренних классов тем, что класс верхнего уровня может напрямую использовать только свои собственные переменные экземпляра.

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

Ответы

Ответ 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. Еще один вопрос по теме, которая может быть интересной.