6

以下文字来自 jls http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3

即便如此,仍有许多并发症。如果在字段声明中将最终字段初始化为编译时常量表达式(第 15.28 节),则可能不会观察到最终字段的更改,因为该最终字段的使用在编译时被常量表达式的值替换.

谁能给我更好的解释以上。我无法理解“可能无法观察到对最终字段的更改”的说法。可以借助示例。

4

3 回答 3

13

我无法理解对 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

于 2013-07-06T21:06:03.953 回答
4

这意味着如果在课堂上你有这个:

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。如果在运行时您稍后通过反射更改了这些最终字段中的任何一个,那么引用它的代码(正如它所编写的那样)可能永远看不到它。

于 2013-07-06T19:35:28.393 回答
0

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 引用的文本说,如果将字段声明为常量,则不会发生这种事情。

于 2020-10-13T12:25:23.837 回答