Подтвердить что ты не робот

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

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

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

4b9b3361

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