在一个导入的Java类中,public static final变量是什么意思?

18

我在工作中偶然遇到了一段Java代码。情况如下:有两个类 - ClassAClassB

ClassA 除了里面有4个公共的静态常量字符串值之外,没有其他东西。它的目的是像这样使用那些值:ClassA.variable(不要问我为什么,那不是我的代码)。

ClassB 导入了 ClassA。我修改了 ClassA 中的字符串值并编译了它。当我运行 ClassB 时,我发现它正在使用旧的值 - 而不是新的值。我不得不重新编译 ClassB 才能让它使用来自 ClassA 的新值!(我还必须重新编译导入 ClassA 的其他类!)

这只是因为JDK 1.6还是我应该早些知道也要重新编译 ClassB?请指教。:)

5个回答

23
如果来自ClassA类的final变量的值恰好是编译时常量,编译器可能会将它们内联到使用ClassA的类中,而不是生成运行时引用。我认为,在您描述的情况下,就是这种情况。
例如:
public class Flags {
    public static final int FOO = 1;
    public static final int BAR = 2;
}

public class Consumer {
    public static void main(String[] args) {
         System.out.println(Flags.FOO);
    }
}
在这个例子中,编译器可能会将FOO的值合并到生成的Consumer代码中,而不是生成等效的运行时引用。如果FOO的值稍后发生更改,则必须重新编译Consumer以便它使用新值。
这是一种优化,具有几个方面的优势,例如提高了编译程序的效率和速度。例如,内联该值可能会使使用它的表达式进一步优化。
int x = Flags.FOO * 10;
在这个例子中,内联值(这里是1)使得编译器注意到乘法没有任何作用,可以完全省略。

1
所以,你的意思是public static final是编译时常量?我不知道。我以为它只是一个常量,不能在运行时修改!感谢你的帮助。 - Senthil Kumar
3
好的回答。如果你想查看变量是否被内联,可以使用javap命令来查看类是如何编译的,例如:“javap -c Flags”。 - John Paulett

3
假设ClassA长这样:
public class ClassA {
    public static final int FOO = 1;
    public static final int BAR = 2;
}

如果你重新编译,ClassB将继续使用旧值。我猜这可能与编译器有关,但我认为这是典型的行为。如果你不想每次ClassA中的常量发生变化就重新编译ClassB,你需要做类似于下面的事情:

public class ClassA {
    public static final int FOO = CONST(1);
    public static final int BAR = CONST(2);

    public static int CONST(int i) { return i; }
}

由于现在的javac不愿意将常量内联,所以当ClassA的静态初始化程序运行时,它将调用CONST(int)方法。

3

这是一个二进制兼容性问题。对常量字段的引用在编译时解析。你看到的行为是正确的;如果你更改A类中的值,则必须重新编译客户端(B类)。为了避免这种问题,考虑使用Java 5.0版本引入的枚举类型添加常量。


2
如果您没有在switch语句中使用值,可以尝试使用以下方法代替:
public class A
{
    public static final int FOO;
    public static final String BAR;

    static
    {
        FOO = 42;
        BAR = "Hello, World!";
    }
}

那么编译器将不再在使用这些值的其他类中硬编码它们。


2

为什么要逐个编译类?

使用像Maven或Ant这样的构建系统,或者让您的IDE自动完成。

唯一安全的做法是重新编译每个依赖于已更改的Java类的Java文件,直到可能受影响的每个类都被重新编译。


其中一个类可能不受你的控制。 - DJClayworth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接