最终字段的初始化顺序

时间:2015-03-12 13:20:08

标签: java constructor final jls

考虑这两个类:

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的一些例外规则,但除非我遗漏了某些内容,否则就没有。

请注意,我知道我永远不应该从构造函数中调用可覆盖的方法。这不是问题的关键。

3 个答案:

答案 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中所述。