每个内部类都需要一个封闭实例,这是真的吗?

38
术语“内部类”通常被认为是“需要封闭实例的嵌套类”。然而,JLS如下所述: 8.1.3.内部类和封闭实例 [...]
内部类包括本地(§14.3)、匿名(§15.9.5)和非静态成员类(§8.5)。
[...]
在静态上下文中声明的内部类的实例没有词法封闭实例。
此外, 15.9.5. 匿名类声明 [...]
匿名类始终是内部类(§8.1.3);它从不是静态的(§8.1.1、§8.5.1)。

众所周知,在静态上下文中可以声明匿名类:

class A {
  int t() { return 1; }
  static A a =  new A() { int t() { return 2; } };
}

简言之,new A() {} 是一个嵌套类,没有封闭的实例,在静态上下文中定义,但它不是一个静态嵌套类,而是一个内部类。

我们在日常使用中是否都赋予这些术语不恰当的含义?

有趣的相关点是,这个历史规范文档将术语“顶层”定义为“内部”的相反:

静态类成员和包成员都被称为顶层类。它们与内部类的区别在于,顶层类只能直接使用自己的实例变量。

然而,在通常的用法中,“顶层”被认为是“嵌套”的相反。


8
看起来术语似乎被不必要地搞得很混乱... - Jon Skeet
3
问题在于,我们都依赖这种术语来进行交流 :) 随着时间的推移,我们似乎已经为自己创造了一种不同的术语,这种术语更加实用。 - Marko Topolnik
4
当然。这是那些情况之一,在这种情况下,我认为规范改变以适应社区是有意义的,而不是相反。 - Jon Skeet
你的最后两点是在删除 static 而不是添加它时会发生的事情。 - Ian Roberts
@IanRoberts 谢谢,已纠正(在答案中,我把那个文本转移了)。 - Marko Topolnik
显示剩余12条评论
3个回答

9

从规范的角度来看,问题提出的区别是完全有意义的:

  • 内部类有一些限制,这些限制与嵌套实例的问题无关(例如,它可能没有静态成员);

  • 静态嵌套类的概念基本上只是关于命名空间的;这些类可以合理地被称为顶级类,与我们通常认为的顶级类一起。

恰巧从嵌套类声明中删除“static”会同时进行两件不同的事情:

  1. 它使类需要一个嵌套实例;
  2. 它使类成为内部类。

我们很少考虑内部类会有什么限制;我们只关注嵌套实例的问题,因为这更加明显。然而,从规范的角度来看,这些限制是一个重要的问题。

我们所缺少的是一个“需要嵌套实例的类”的术语。JLS没有定义这样的术语,所以我们(似乎)已经使用了一个相关但实际上基本不同的术语来表示它。


远非如此:您对名称解析语义、this的语义以及许多其他微妙之处进行了重要更改。 - Marko Topolnik
1
相关的是,如果Java有一个功能可以让我不再写getter、setter、equals、hashCode和toString,那对我来说将会产生巨大的影响——至少在我的预期实现是众所周知的默认情况下。 - Marko Topolnik
1
对我来说,静态和非静态比内部和嵌套更容易理解。它们定义得更清晰。如果使用内部/嵌套术语阅读JLS将会很痛苦。 - Mikhail
1
如果你所说的“非静态”是指“没有封闭实例”,那么你的区分就不起作用了——这就是这个问题的关键 :) 存在一种没有封闭实例但仍然非静态的嵌套类! - Marko Topolnik
1
“局部类”的概念与封闭实例无关。有些有,有些没有。在你的答案中,“StaticInner”是一个没有封闭实例的嵌套类的例子。 - Marko Topolnik
显示剩余10条评论

0

那么,在你的情况下,匿名类是否也有封闭实例呢?是引用是静态的,而不是匿名类的实例。考虑:

class A {
   int t() { return 1; }
   static A a = new A() { { System.out.println(t()); } };
}

2
请注意 this 和我提到的第一个封闭实例(也称为“零封闭实例”)之间的区别,它被简写为“封闭实例”。 - Marko Topolnik

-2

静态内部类和非静态内部类没有区别,我不明白为什么要将它们分开考虑。请看下面的代码:

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();
    }
}

然后看一下 StaticInnerInner 类的字节码:

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. 另一个相关主题的问题,可能很有趣。


6
你所称之为"StaticInner"的并不是内部类,它是嵌套类,但这与我的问题无关。而且,是的,区别只在源代码级别上可见,在编译器强制执行的规则中。字节码是相同的,因为这是Java 1.1引入内部类时的设计要求。 - Marko Topolnik
3
一个带有封闭实例的类所产生的句法后果并不是小意思。如果你认为每个句法特征都是无关紧要的话,那么所有图灵完备语言在你眼中都是一样的。例如,汇编语言和 Haskell 处于同等地位。 - Marko Topolnik
基本上你是对的,但不要把事情夸大到荒谬的地步。 - Mikhail
4
当然,引入一个荒谬的条款只是为了帮助我证明我的观点。如果字面上理解这种特定立场,我只是在指出它可以被推到什么程度。 - Marko Topolnik
2
@Mikhail 这不是荒谬的,而是正确的术语。 - Jossie Calderon
区别在于,“非静态内部”是一个自相矛盾的说法。请参见问题中引用的JLS 8.1.3。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接