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

Posted

技术标签:

【中文标题】每个内部类都需要一个封闭实例是真的吗?【英文标题】:Is it true that every inner class requires an enclosing instance? 【发布时间】:2013-12-26 10:59:33 【问题描述】:

术语内部类通常被认为是指“需要封闭实例的嵌套类”。但是,JLS 声明如下:

8.1.3. Inner Classes and Enclosing Instances

[...]

内部类包括本地(§14.3)、匿名(§15.9.5)和非静态成员类(§8.5)。

[...]

在静态上下文中声明的内部类的实例没有词法封闭实例。

还有,

15.9.5. Anonymous Class Declarations

[...]

匿名类始终是内部类(第 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() 是一个没有封闭实例的嵌套类,在静态上下文中定义,但它不是静态嵌套类——它是一个内部类。

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

作为一个相关的兴趣点,this historical specification document 将术语 top-level 定义为 inner 的对立面:

static 类成员的类和哪些类 是包成员都称为***类。它们不同于内 ***类只能直接使用它自己的实例 变量。

而在常见用法中,top-level 被视为与 nested 相反。

【问题讨论】:

看起来术语已经被不必要地混淆了...... 问题是,我们都依赖该术语进行交流 :) 随着时间的推移,我们似乎已经为自己制定了一个不同的术语,它更有用。 当然。这是我认为改变规范以匹配社区而不是相反的情况之一。 你最后的第 1 点和第 2 点是当你 删除 static 而不是当你添加它时发生的。 @IanRoberts 谢谢,已更正(在答案中,我已经转移了该文本)。 【参考方案1】:

从规范的角度来看,问题中列出的区别非常合理:

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

静态嵌套类的概念基本上就是命名空间;这些类可能与我们通常认为的***类一起被称为***

碰巧从嵌套类声明中删除 static 会同时做两件不同的事情:

    它使类需要一个封闭的实例; 它使类内部

我们很少将 inner 视为包含限制;我们只关注 enclosure instance 关注点,它更加明显。但是,从规范的角度来看,这些限制是一个至关重要的问题。

我们缺少的是需要封闭实例的类的术语。 JLS 没有定义这样的术语,所以我们(似乎不知道)劫持了一个相关但实际上本质上不同的术语来表示这个。

【讨论】:

通过删除单词static,您只需为类添加一个隐式构造函数。为什么这么有意义?您始终可以显式创建完全相同的构造函数。 在相关的说明中,如果 Java 有一个特性可以让我不必编写 getter、setter、equalshashCode、和toString 再一次——至少当我的预期实现是众所周知的默认值时。 对我来说静态和非静态比内部和嵌套更容易理解。他们对事物的定义更加清晰。使用内部/嵌套术语阅读 JLS 会很痛苦。 如果“非静态”的意思是“没有封闭的实例”,那么你的区别就不起作用了——如果这个问题就是这样:) 没有封闭实例的嵌套类,它仍然是非静态的 “本地类”的概念再次与封闭实例无关。有些有,有些没有。在您的回答中,StaticInner 是没有封闭实例的嵌套类的示例。【参考方案2】:

好吧,在您的情况下,匿名类不也有封闭实例吗?它是静态的引用,而不是匿名类的实例。考虑:

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

【讨论】:

请注意this(也称为第零个封闭实例)和我正在讨论的第一个封闭实例之间的区别,它简称为“封闭实例”。【参考方案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();
    

然后在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

实际上它们之间根本没有区别。我想说非静态内部类只是语法糖。写普通东西的更短的方法,仅此而已。唯一的细微差别是,在非静态内部类中,对封闭类的引用是在调用父构造函数之前分配的,这可能会影响一些逻辑,但我认为这不是很关键,分开考虑。

附: One more question on a related topic, which might be interesting.

【讨论】:

你所说的StaticInner 根本不是内部类。它是嵌套的,但这与我的问题无关。是的,差异仅在源代码级别可见,在编译器强制执行的规则中。字节码是相同的,因为这是在 Java 1.1 中引入内部类时的设计要求。 具有封闭实例的类的句法后果一点也不小。如果你认为每个句法特征都是无关紧要的,那么所有图灵完备的语言在你眼中都是一样的。例如,Assembler 与 Haskell 处于同等地位。 基本上你是对的,但不要走到荒谬的地步。 当然,引入一个荒谬的从句只是帮助阐明我的观点的工具。我只是指出,如果从字面上看,这种特定的观点可以走多远。 @Mikhail 这并不荒谬,这是正确的术语。

以上是关于每个内部类都需要一个封闭实例是真的吗?的主要内容,如果未能解决你的问题,请参考以下文章

笔试错题(典型题)

为啥扩展内部类的本地类不能访问内部类封闭实例?

5.内部类

动态实例化嵌套在抽象类中的内部类

内部类成员从封闭类的可访问性

如何做到类的线程安全