这是代码:
public class Main {
public static void main(String[] args) {
new B();
}
}
class A {
A() {
System.out.println("A constructor before");
action();
System.out.println("A constructor after");
}
protected void action() {
System.out.println("Never called");
}
}
class B extends A {
private final int finalField = 42;
private int field = 99;
B() {
System.out.println("B constructor");
action();
}
public void action() {
System.out.println("B action, finalField=" + finalField + ", field=" + field);
}
}
Run Code Online (Sandbox Code Playgroud)
结果是:
A constructor before
B action, finalField=42, field=0
A constructor after
B constructor
B action, finalField=42, field=99
Run Code Online (Sandbox Code Playgroud)
我对这一行感到困惑:
B action, finalField=42, field=0
Run Code Online (Sandbox Code Playgroud)
对象 B 未完全初始化,当我们从超类构造函数调用方法“action”时,变量“field”具有默认值,但最终变量“finalField”已经具有值 42。
“finalField”何时初始化?
当使用常量表达式(15.29)初始化最终字段时,它被称为常量变量(4.12.4):
\nprivate final int finalField = 42;\nRun Code Online (Sandbox Code Playgroud)\n这意味着字符串
\n"B action, finalField=" + finalField + ", field="\nRun Code Online (Sandbox Code Playgroud)\n本身是一个常量表达式,其值在编译时确定。如果您检查编译后的类文件,您实际上会B action, finalField=42, field=在常量池部分中找到该字符串。
一般来说,当使用常量变量的字段时,必须在编译时将其替换为其值。不允许(13.1)在运行时引用该字段:
\n\n\n\n
\n- \n
对常量变量 (\xc2\xa74.12.4) 字段的引用必须在编译时解析为由常量变量的初始值设定项表示的值 V。
\n如果这样的字段是非静态的,则二进制文件的代码中不应存在对该字段的引用,包含该字段的类除外。(它将是一个类而不是一个接口,因为接口仅具有静态字段。)该类应该具有在实例创建期间将字段的值设置为 V 的代码 (\xc2\xa712.5)。
\n
字段初始值设定项仍会在您期望的时间运行:在A构造函数返回之后和之前B构造函数启动之前。观察未初始化的值很棘手,因为编译器内联了该变量的使用,但您可以通过反射访问该字段的值:
public void action() {\n try {\n System.out.println("B action, finalField="\n + getClass().getDeclaredField("finalField").get(this)\n + ", field=" + field);\n } catch (IllegalAccessException | NoSuchFieldException e) {\n e.printStackTrace();\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n输出:
\nA constructor before\nB action, finalField=0, field=0\nA constructor after\nB constructor\nB action, finalField=42, field=99\nRun Code Online (Sandbox Code Playgroud)\n