带有静态和最终限定符的奇怪Java行为

带有静态和最终限定符的奇怪Java行为,java,final,Java,Final,在我们的团队中,我们发现了一些奇怪的行为,我们使用了static和final限定符。这是我们的测试课: 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)

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

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
值,我会理解,因为静态类成员的代码是从上到下执行的


有人能解释为什么会发生这种行为吗?

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


如果将
I
的类型更改为
int
,您也会看到同样的情况。

以下是运行程序时采取的步骤:

  • 在运行
    main
    之前,必须通过按出现顺序运行静态初始值设定项来初始化
    Test
  • 要初始化
    me
    字段,请开始执行
    newtest()
  • 打印
    I
    的值。由于字段类型为
    Integer
    ,因此编译时常量
    4
    将成为计算值(
    Integer.valueOf(4)
    )。此字段的初始值设定项尚未运行,正在打印初始值
    null
  • 打印
    S
    的值。因为它是用编译时常量初始化的,所以该值被烘焙到引用站点中,打印
    abc
  • newtest()
    完成,现在执行
    I
    的初始值设定项

  • 教训:如果您依赖于急切初始化的静态单例,请将单例声明作为最后一个静态字段声明,或者求助于在所有其他静态声明之后出现的静态初始值设定项块。这将使类看起来完全初始化为单例的构造代码。

    由于数据类型为
    Integer
    ,您会有奇怪的行为。静态字段是按照您编写它的顺序初始化的,但编译时常量是先初始化的


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

    您的
    测试编译为:

    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
    之前被调用。这就是它为
    I
    打印
    的原因。如果将声明顺序交换为
    me
    I
    ,则会得到预期的结果,因为
    I
    将在调用构造函数之前初始化。您还可以将
    I
    的类型从
    Integer
    更改为
    int

    因为
    4
    需要自动装箱(即,包装在
    整数
    对象中),所以它不是编译时常量,是静态初始值设定项块的一部分。但是,如果类型为
    int
    ,则编号
    4
    将是编译时常量,因此不需要显式初始化。由于
    “abc”
    是一个编译时常量,因此
    S
    的值按预期打印

    如果你愿意替换

    public static final String S = "abc";
    
    有,


    然后您会注意到
    S
    的输出也是
    “null”
    。为什么会这样?出于同样的原因,
    I
    也会输出
    “null”
    。此类字段具有文本常量值(不需要自动装箱,如
    String
    )在编译时使用
    “ConstantValue”
    属性进行属性化,这意味着只需查看类的常量池即可解析其值,而无需运行任何代码。

    相关(和)你可以看看答案。希望有帮助。另一个混乱归因于Java的特权类型。现在标记为重复…这是新闻稿的效果吗?确定吗?我希望
    System.out.println(“abc”),而不是
    System.out.println,因为编译器内联编译时常量。你说得对。在前一种情况下,将有一条
    ldc
    指令,而不是
    getstatic
    。这就是我在最后一句话中想表达的意思,我想我应该澄清一下。你的代码应该表明这一点,因为你说过OPs是编译成这个的。@Tom,这是一个没有区别的区别
    S
    “abc”
    @EJP的另一个名称,这里可能没有,因为声明和访问都在同一个类中,但如果它们在不同的类中,则必须将它们区分开来。
    public static final String S = new String("abc");