Java final 字段编译时常量表达式
Posted
技术标签:
【中文标题】Java final 字段编译时常量表达式【英文标题】:Java final field compile-time constant expression 【发布时间】:2013-07-04 14:04:46 【问题描述】:以下文字来自jlshttp://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3
即便如此,也存在许多并发症。如果最终字段是 初始化为编译时常量表达式(§15.28) 字段声明,可能不会观察到对最终字段的更改, 因为该 final 字段的使用在编译时被替换为 常量表达式的值。
谁能给我更好的解释以上。我无法理解语句“可能不会观察到对最终字段的更改”。可能在示例的帮助下。
【问题讨论】:
【参考方案1】:我无法理解对 final 字段的语句更改可能不会被观察到
它告诉我们,如果最终变量被声明为编译时常量,那么在程序中进一步使用 reflection API 对最终变量所做的任何更改都不会在执行期间对程序可见。 例如考虑下面给出的代码:
import java.lang.reflect.*;
class ChangeFinal
private final int x = 20;//compile time constant
public static void change(ChangeFinal cf)
try
Class clazz = ChangeFinal.class;
Field field = clazz.getDeclaredField("x");
field.setAccessible(true);
field.set(cf , 190);//changed x to 190 for object cf
catch (Exception ex)
ex.printStackTrace();
public static void main(String[] args)
ChangeFinal cf = new ChangeFinal();
System.out.println(cf.x);//prints 20
change(cf);
System.out.println(cf.x);//prints 20
以上代码的输出为:
20
20
为什么?
答案在于javap -c
命令为 public static void main 提供的输出:
public static void main(java.lang.String[]);
Code:
0: new #3; //class ChangeFinal
3: dup
4: invokespecial #11; //Method "<init>":()V
7: astore_1
8: getstatic #12; //Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_1
12: invokevirtual #13; //Method java/lang/Object.getClass:()Ljava/lang/Cla
ss;
15: pop
16: bipush 20
18: invokevirtual #14; //Method java/io/PrintStream.println:(I)V
21: aload_1
22: invokestatic #15; //Method change:(LChangeFinal;)V
25: getstatic #12; //Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_1
29: invokevirtual #13; //Method java/lang/Object.getClass:()Ljava/lang/Cla
ss;
32: pop
33: bipush 20
35: invokevirtual #14; //Method java/io/PrintStream.println:(I)V
38: return
在第 16 行(在调用 changeFinal
方法之前)cf.x
的值被硬编码为 20
。在第 33 行(在调用 changeFinal
方法之后)cf.x
的值再次被硬编码为 20
。因此,虽然最终变量x
的值的变化是由reflection API
在执行过程中成功完成的,但是由于x
是一个编译时常量,它显示了它的常量值20
。
【讨论】:
【参考方案2】:这意味着如果在课堂上你有这个:
public class Foo
public final boolean fooBoolean = true; // true is a constant expression
public final int fooInt = 5; // 5 is a constant expression
在编译时,任何对Foo.fooBoolean
的引用都可以替换为true
,对Foo.fooInt
的引用可以替换为5
。如果在运行时您稍后通过反射更改了这些最终字段中的任何一个,那么引用它的代码(正如它所写的那样)可能永远看不到它。
【讨论】:
另一个例子是如果你在上面的类中将 fooInt 更改为 6,然后重新编译这个 Foo 类,而不重新编译使用 foo 类的类。那么这些其他类可能仍会将 fooInt 读取为 5 而不是 6。 @MTilsted 好例子。真的很混乱。【参考方案3】:Java 程序很可能在不同时间观察到具有两个不同值的final
字段,即使没有反射,没有重新编译类的多个版本,也没有任何类似的东西。考虑下面的类:
class X
static final int x = getX();
static int getX()
System.out.println("X.x is now " + X.x);
return 1;
public static void main(String[] args)
System.out.println("X.x is now " + X.x);
输出:
X.x is now 0
X.x is now 1
出现这种情况是因为部分代码(第一个println
)在字段赋值之前执行,所以代码观察到字段的默认初始值0。字段在赋值之前有一个默认初始值, 即使它是最终的,因为它不是一个常量字段。您从 JLS 中引用的文本说,如果将字段声明为常量,则不会发生这种情况。
【讨论】:
以上是关于Java final 字段编译时常量表达式的主要内容,如果未能解决你的问题,请参考以下文章