编译时代码中是否替换了Java静态最终值?

在java中,说我有以下内容

==fileA.java== class A { public static final int SIZE = 100; } 

然后在另一个文件中我使用此值

 ==fileB.java== import A; class b { Object[] temp = new Object[A.SIZE]; } 

当这个被编译时, SIZE被替换为值100,所以如果我要在路上替换FileA.jar而不是FileB.jar,对象数组会获得新值,还是硬编码为100,因为那样它最初建造时的价值?

谢谢,
斯蒂芬妮

是的,Java编译器确实将示例中的SIZE等静态常量值替换为其文字值。

因此,如果您稍后在A类中更改SIZE但不重新编译类b ,您仍将在类b看到旧值。 你可以轻松测试一下:

文件A.java

 public class A { public static final int VALUE = 200; } 

文件B.java

 public class B { public static void main(String[] args) { System.out.println(A.VALUE); } } 

编译A.java和B.java。 现在运行: java B

更改A.java中的值。 重新编译A.java,但不重新编译B.java。 再次运行,您将看到打印旧值。

你可以通过这样做来保持常量不被编译成B.

 class A { public static final int SIZE; static { SIZE = 100; } } 

另一种certificate行为是查看生成的字节码的途径。 当常量为“小”(大概<128)时:

 public B(); Code: 0: aload_0 1: invokespecial #10; //Method java/lang/Object."":()V 4: aload_0 5: bipush 42 7: anewarray #3; //class java/lang/Object 10: putfield #12; //Field temp:[Ljava/lang/Object; 13: return } 

(我用了42而不是100,所以它更突出)。 在这种情况下,它在字节代码中明显地被替换。 但是,说常数是“更大”。 然后你得到如下所示的字节代码:

 public B(); Code: 0: aload_0 1: invokespecial #10; //Method java/lang/Object."":()V 4: aload_0 5: ldc #12; //int 86753098 7: anewarray #3; //class java/lang/Object 10: putfield #13; //Field temp:[Ljava/lang/Object; 13: return 

当它更大时,使用操作码“ldc”,根据JVM文档 “无符号字节,它必须是当前类的运行时常量池的有效索引”。

在任何一种情况下,常量都嵌入到B.我想,因为在操作码中你只能访问当前类的运行时常量池,这就是将常量写入类文件的决定与实现无关(但我不知道)我知道这个事实)。

哇 – 你每天都学到新东西!

取自Java规范……

注意:如果将基本类型或字符串定义为常量并且该值在编译时已知,则编译器会将代码中的常量名称替换为其值。 这称为编译时常量。 如果外部世界中常量的值发生变化(例如,如果立法规定pi实际上应该是3.975),则需要重新编译使用此常量来获取当前值的任何类。

这里的重要概念是static final字段使用编译时常量初始化,如JLS中所定义。 使用非常量初始化(或非static或非final )并且不会被复制:

 public static final int SIZE = null!=null?0: 100; 

null不是*编译时常量`。)

实际上我不久前遇到了这种奇怪的现象。

这将直接将“100”编译成b类。 如果您只是重新编译A类,则不会更新B类中的值。

最重要的是,编译器可能没有注意到重新编译类b(在我编译单个目录时,类B在一个单独的目录中并且编译一个目录没有触发B的编译)

作为优化,编译器将内联最终变量。

所以在编译时它看起来像。

 class b { Object[] temp = new Object[100]; } 

有一点需要注意:静态最终值在编译时是已知的,如果在编译时未知该值,编译器将不会在代码中的任何位置用其值替换常量名称。

  public class TestA { // public static final int value = 200; public static final int value = getValue(); public static int getValue() { return 100; } } public class TestB { public static void main(String[] args) { System.out.println(TestA.value); } } 

首先编译TestA和TestB, 运行TestB

然后改变TestA.getValue()返回200,编译TestA, 运行TestB ,TestB将获取新值, 在此处输入图像描述

有一个例外: –

如果静态final字段在编译时为null,则它不会被null替换(实际上是它的值)

A.java

 class A{ public static final String constantString = null; } 

B.java

 class B{ public static void main(String... aa){ System.out.println(A.constantString); } } 

编译A.java和B.java并运行java B.

输出将为null


现在使用以下代码更新A.java并仅编译此类。

 class A{ public static final String constantString = "Omg! picking updated value without re-compilation"; } 

现在运行java B.

输出将是Omg! 选择更新的值而无需重新编译

Java确实优化了这些类型的值,但前提是它们位于同一个类中。 在这种情况下,由于您正在考虑的用例,JVM在A.SIZE中查找而不是优化它。