编译时代码中是否替换了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中查找而不是优化它。