考虑这两个类:
public abstract class Bar {
protected Bar() {
System.out.println(getValue());
}
protected abstract int getValue();
}
public class Foo extends Bar {
private final int i = 20;
public Foo() {
}
@Override
protected int getValue() {
return i;
}
public static void main(String[] args) {
new Foo();
}
}
如果我执行Foo,则输出为20。
如果我将字段设为非final,或者如果我在Foo构造函数中初始化它,则输出为0.
我的问题是:在最终字段的情况下,初始化顺序是什么?JLS中描述了这种行为的位置?
我希望找到关于最终字段here的一些例外规则,但除非我遗漏了某些内容,否则就没有。
请注意,我知道我永远不应该从构造函数中调用可覆盖的方法。这不是问题的关键。
答案 0 :(得分:18)
您的final int i
成员变量是一个常量变量:4.12.4. final
Variables
基本类型或类型
String
的变量,即final
,并使用编译时常量表达式(第15.28节)初始化,称为常量变量。
这会影响事物初始化的顺序,如12.4.2. Detailed Initialization Procedure中所述。
答案 1 :(得分:4)
以“字节代码”的方式向您介绍它的外观。
您应该已经知道,构造函数中的第一个实际指令必须是对super
的调用(无论是否有参数)。
当父构造函数完成并且超级“对象”完全构造时,该超级指令返回。因此,当您构造Foo
时,会发生以下情况(按顺序):
// constant fields are initialized by this point
Object.construction // constructor call of Object, done by Bar
Bar.construction // aka: Foo.super()
callinterface getValue() // from Bar constructor
// this call is delegated to Foo, since that's the actual type responsible
// and i is returned to be printed
Foo.construction
如果你要在构造函数中初始化它,那么在getValue()
被调用之后就会“现在”发生。
答案 2 :(得分:0)
这是一个更加偷偷摸摸的版本,只是为了好玩。
public abstract class Bar {
protected Bar() {
System.out.println(getValue());
}
protected abstract Object getValue();
}
public class Foo extends Bar {
private final String i = "Hello";
public Foo() {
}
@Override
protected Object getValue() {
return i;
}
public static void main(String[] args) {
new Foo();
}
}
结果:打印Hello
。
现在改变这一行:
private final String i = "Hello";
为:
private final Object i = "Hello";
结果:打印null
。
这是因为我在其他答案中提到的String
(和原始类型)是专门处理的,如JLS 4.12.4中所述。