Java:匿名类的初始化和构造函数

ugo*_*ugo 5 java constructor javac anonymous-class javap

我想了解一个我在处理匿名课程时遇到的奇怪行为.

我有一个类在其构造函数中调用受保护的方法(我知道,设计很差,但这是另一个故事...)

public class A {
  public A() {
    init();
  }
  protected void init() {}
}
Run Code Online (Sandbox Code Playgroud)

然后我有另一个扩展A和覆盖的类init().

public class B extends A {
  int value;
  public B(int i) {
    value = i;
  }
  protected void init() {
    System.out.println("value="+value);
  }
}
Run Code Online (Sandbox Code Playgroud)

如果我编码

B b = new B(10);
Run Code Online (Sandbox Code Playgroud)

我明白了

> value=0
Run Code Online (Sandbox Code Playgroud)

这是预期的,因为超级类的构造函数在Bctor 之前调用然后value仍然是.

但是在使用像这样的匿名类时

class C {
  public static void main (String[] args) {
    final int avalue = Integer.parsetInt(args[0]);
    A a = new A() {
      void init() { System.out.println("value="+avalue); }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我希望得到value=0因为它应该或多或少等于类B:编译器自动创建一个新类C$1,扩展A并创建实例变量来存储匿名类的方法中引用的局部变量,模拟闭包等...

但是当你运行这个时,我得到了

> java -cp . C 42
> value=42
Run Code Online (Sandbox Code Playgroud)

最初我认为这是因为我使用的是java 8,也许在介绍lamdbas时,他们改变了匿名类的实现方式(你不再需要final),但我试过java 7也得到了同样的结果......

事实上,与看字节码javap,我可以看到,B

> javap -c B
Compiled from "B.java"
public class B extends A {
  int value;

  public B(int);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method A."<init>":()V
       4: aload_0
       5: iload_1
       6: putfield      #2                  // Field value:I
       9: return
...
Run Code Online (Sandbox Code Playgroud)

同时为C$1:

> javap -c C\$1
Compiled from "C.java"
final class C$1 extends A {
  final int val$v;

  C$1(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #1                  // Field val$v:I
       5: aload_0
       6: invokespecial #2                  // Method A."<init>":()V
       9: return
....
Run Code Online (Sandbox Code Playgroud)

有人能告诉我为什么会有这种差异吗 有没有办法使用"普通"类复制匿名类的行为?

编辑:澄清问题:为什么匿名类的初始化会破坏初始化任何其他类的规则(在设置任何其他变量之前调用超级构造函数)?或者,有没有办法B在inovking超级构造函数之前在类中设置实例变量?

Zho*_*gYu 3

这个问题适用于所有内部类,而不仅仅是匿名类。(匿名类是内部类)

JLS 没有规定内部类体如何访问外部局部变量;它仅指定局部变量实际上是最终的,并且在内部类主体之前明确分配。因此,按理说,内部类必须看到局部变量的明确赋值。

JLS 没有具体指定内部类如何看待该值;编译器可以使用任何技巧(在字节码级别上可能的)来实现该效果。特别是,这个问题与构造函数完全无关(就语言而言)。

类似的问题是内部类如何访问外部实例。这个有点复杂,确实和构造函数有关系。尽管如此,JLS仍然没有规定编译器是如何实现的;该部分包含一条注释:“...编译器可以按照自己的意愿表示立即封闭的实例。Java 编程语言不需要...”


从 JMM 的角度来看,这种规格不足可能是一个问题;目前尚不清楚内部类中的写入与读取是如何进行的。可以合理地假设,对合成变量进行写入,这是在操作之前(按编程顺序)new InnerClass() ;内部类读取合成变量以查看外部局部变量或封闭实例。


有没有办法使用“普通”类来复制匿名类的行为?

您可以将“正常”班级安排为外部内部班级

public class B0
{
    int value;
    public B0(int i){ value=i; }

    public class B extends A
    {
        protected void init()
        {
            System.out.println("value="+value);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它将像这样使用,打印10

    new B0(10).new B();
Run Code Online (Sandbox Code Playgroud)

可以添加一个方便的工厂方法来隐藏语法的丑陋

    newB(10);

public static B0.B newB(int arg){ return new B0(arg).new B(); }
Run Code Online (Sandbox Code Playgroud)

所以我们把我们的班级分成两部分;外部部分甚至在超级构造函数之前执行。这在某些情况下很有用。(另一个例子)


(内部匿名访问局部变量封闭实例有效的最终超级构造函数)