我有一个类,在其构造函数中调用了一个受保护的方法(我知道,这是一个糟糕的设计,但那是另外一个故事...)。
public class A {
public A() {
init();
}
protected void init() {}
}
然后我有另一个类,它继承了A
并重写了init()
方法。
public class B extends A {
int value;
public B(int i) {
value = i;
}
protected void init() {
System.out.println("value="+value);
}
}
如果我编写代码
B b = new B(10);
我理解
> value=0
这是正常的,因为在B
构造函数调用之前,父类的构造函数被调用,此时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); }
}
}
}
我期望得到value=0
,因为这应该与B
类相当:编译器会自动创建一个新的继承A
的类C$1
,并创建实例变量来存储在匿名类方法中引用的本地变量,模拟闭包等等……
但是运行时却得到了不同结果。
> java -cp . C 42
> value=42
起初,我认为这是因为我使用的是Java 8,也许在引入Lambda表达式时,匿名类的实现方式在底层有所改变(您不再需要使用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
...
当 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
....
有人能告诉我这个差异的原因吗? 是否有一种方法可以使用“普通”类来复制匿名类的行为?
编辑:
澄清问题:为什么匿名类的初始化违反了初始化任何其他类的规则(在设置任何其他变量之前调用超级构造函数)?
或者,是否有一种方法可以在调用超级构造函数之前在B
类中设置实例变量?
C$1
类是一个特例,如果它不遵循标准构造函数规则也没关系。这很合理,但在我看来还是有点尴尬。 - ugo