使用静态和最终限定符的奇怪Java行为

时间:2016-09-12 10:22:49

标签: java final

在我们的团队中,我们发现了一些奇怪的行为,我们使用了staticfinal限定符。这是我们的测试类:

public class Test {

    public static final Test me = new Test();
    public static final Integer I = 4;
    public static final String S = "abc";

    public Test() {
        System.out.println(I);
        System.out.println(S);
    }

    public static Test getInstance() { return me; }

    public static void main(String[] args) {
        Test.getInstance();
    }
} 

当我们运行main方法时,我们得到以下结果:

null
abc

我会理解它是否同时写入null值,因为静态类成员的代码是从上到下执行的。

任何人都可以解释为什么会发生这种情况吗?

4 个答案:

答案 0 :(得分:109)

这些是您运行程序时采取的步骤:

  1. 在运行main之前,必须通过按照外观顺序运行静态初始化程序来初始化Test类。
  2. 要初始化me字段,请开始执行new Test()
  3. 打印I的值。由于字段类型为Integer,因此看起来像编译时常量4的变为计算值(Integer.valueOf(4))。此字段的初始化程序尚未运行,打印初始值null
  4. 打印S的值。由于它是使用编译时常量初始化的,因此该值将被引入引用站点,并打印abc
  5. new Test()完成,现在I的初始化程序已执行。
  6. 课程:如果您依赖于急切初始化的静态单例,请将单例声明放在最后一个静态字段声明中,或者使用在所有其他静态声明之后发生的静态初始化程序块。这将使类完全初始化为单例的构造代码。

答案 1 :(得分:71)

S是一个编译时常量,遵循JLS 15.28的规则。因此,代码中S的任何出现都将替换为编译时已知的值。

如果您将I的类型更改为int,您也会看到相同的内容。

答案 2 :(得分:21)

由于Integer数据类型,您的行为很奇怪。关于JLS 12.4.2静态字段按您编写的顺序初始化,但首先初始化编译时常量。

如果您不使用包装类型Integer但使用int类型,则可以获得所需的行为。

答案 3 :(得分:14)

您的Test汇编成:

public class Test {

    public static final Test me;
    public static final Integer I;
    public static final String S = "abc";

    static {
        me = new Test();
        I = Integer.valueOf(4);
    }

    public Test() {
        System.out.println(I);
        System.out.println("abc");
    }

    public static Test getInstance() { return me; }

    public static void main(String[] args) {
        Test.getInstance();
    }
}

如您所见,Test的构造函数在初始化I之前被调用。这就是为"null"打印I的原因。如果要交换meI的声明顺序,则会得到预期的结果,因为在调用构造函数之前将初始化I。您还可以将I的类型从Integer更改为int

因为4需要进行自动装箱(即包装在Integer对象中),所以它不是编译时常量,而是静态初始化程序块的一部分。但是,如果类型为int,则数字4将是编译时常量,因此不需要显式初始化。由于"abc"是编译时常量,因此S的值将按预期打印。

如果你要替换,

public static final String S = "abc";

用,

public static final String S = new String("abc");

然后您会注意到S的输出也是"null"。为什么会这样?出于同样的原因,I也会输出"null"。这些具有文字,常量值(需要自动装箱,如String)的字段在编译时会被"ConstantValue"属性归因,这意味着它们的值可以是只需通过查看课程即可解决问题。常量池,无需运行任何代码。