以下文字来自 jls http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3
即便如此,仍有许多并发症。如果在字段声明中将最终字段初始化为编译时常量表达式(第 15.28 节),则可能不会观察到最终字段的更改,因为该最终字段的使用在编译时被常量表达式的值替换.
谁能给我更好的解释以上。我无法理解“可能无法观察到对最终字段的更改”的说法。可以借助示例。
以下文字来自 jls http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3
即便如此,仍有许多并发症。如果在字段声明中将最终字段初始化为编译时常量表达式(第 15.28 节),则可能不会观察到最终字段的更改,因为该最终字段的使用在编译时被常量表达式的值替换.
谁能给我更好的解释以上。我无法理解“可能无法观察到对最终字段的更改”的说法。可以借助示例。
我无法理解对 final 字段的语句更改可能不会被观察到
它告诉我们,如果最终变量被声明为编译时常量,那么在程序中进一步使用反射 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
. 因此,虽然 final 变量的值的变化在执行过程中x
成功完成 reflection API
,但由于x
是编译时常量,它显示了它的常量值20
。
这意味着如果在课堂上你有这个:
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
。如果在运行时您稍后通过反射更改了这些最终字段中的任何一个,那么引用它的代码(正如它所编写的那样)可能永远看不到它。
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。该字段在分配之前具有默认初始值,即使它是 final,因为它不是一个常数场。您从 JLS 引用的文本说,如果将字段声明为常量,则不会发生这种事情。