最终字段的初始化顺序

2022-09-03 13:28:28

请考虑以下两个类:

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。

如果我使该字段成为非最终字段,或者如果我在 Foo 构造函数中初始化它,则输出为 0。

我的问题是:在最终字段的情况下,初始化顺序是什么,JLS中描述的这种行为在哪里?

我本以为会在这里找到一些关于最终字段的特殊规则,但除非我错过了什么,否则就没有。

请注意,我知道我永远不应该从构造函数调用可重写的方法。这不是问题的重点。


答案 1

您的成员变量是常量变量:4.12.4。最后变量final int i

基元类型或类型的变量,即使用编译时常量表达式 (§15.28) 进行初始化,称为常量变量Stringfinal

这对初始化事物的顺序有影响,如 12.4.2 中所述。详细的初始化过程


答案 2

以“字节码式”的方式引导您了解这一点。

您应该已经知道,构造函数中的第一个实际指令必须是对的调用(无论是否带有参数)。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()