Java final字段编译时常量表达式

时间:2013-07-06 19:26:36

标签: java final jls

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

Even then, there are a number of complications. If a final field is 
initialized to a compile-time constant expression (§15.28) in the field 
declaration, changes to the final field may not be observed, since uses of that 
final field are replaced at compile time with the value of the constant 
expression.

任何人都可以给我更好的解释。我无法理解语句 changes to the final field may not be observed 。可以借助例子。

提前致谢

3 个答案:

答案 0 :(得分:10)

  

我无法理解声明可能无法观察到对最终字段的更改

它告诉我们,如果最终变量被声明为编译时常量,那么在程序执行期间程序中将无法使用程序中的反射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

<强> WHY吗
答案在于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是编译时常量,它显示其常量值{{1 }}

答案 1 :(得分: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的任何引用都可以替换为trueFoo.fooInt的引用可能会被5替换。如果在运行时您稍后通过反射更改了这些最终字段中的任何一个,则引用它的代码(在写入时)可能永远不会看到它。

答案 2 :(得分: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。在此字段之前,该字段具有默认的初始值。被分配为,即使它是最终的,因为它不是恒定字段。您从JLS引用的文本说,如果将该字段声明为常量,则不会发生这种情况。