是否有可能禁用javac的静态最终variables内联?

Java静态编译器(javac)内嵌了一些静态的最终variables,并将这些值直接传递给常量池。 考虑下面的例子。 类A定义了一些常量(公共静态最终variables):

public class A { public static final int INT_VALUE = 1000; public static final String STRING_VALUE = "foo"; } 

B类使用这些常量:

 public class B { public static void main(String[] args) { int i = A.INT_VALUE; System.out.println(i); String s = A.STRING_VALUE; System.out.println(s); } } 

当你编译B类时,javac从A类获取这些常量的值,并在B.class中内联这些值。 结果,编译时依赖项B必须从类字节码中被擦除。 这是一个非常奇怪的行为,因为在编译时你正在烘焙这些常量的值。 而且你会认为这是JIT编译器在运行时可以做的最简单的事情之一。

有什么办法或任何隐藏的编译器选项,可以让你禁用这种内联行为的javac? 对于背景,我们正在研究为了依赖目的而进行字节码分析,这是字节码分析无法检测编译时依赖性的less数情况之一。 谢谢!

编辑 :这是一个令人头痛的问题,因为通常我们不控制所有的来源(例如定义常量的第三方库)。 我们有兴趣从使用常量的angular度检测这些依赖关系。 由于从使用常量的代码中删除了引用,所以没有简单的方法来检测它们,而不是做源代码分析。

Java Puzzlers的项目93(Joshua Bloch)说,你可以通过防止最终值被认为是一个常量来解决这个问题。 例如:

 public class A { public static final int INT_VALUE = Integer.valueOf(1000).intValue(); public static final String STRING_VALUE = "foo".toString(); } 

当然,如果你不能访问定义常量的代码,那么这些都是不相关的。

我不这么认为。 最简单的解决方法是将这些作为属性而不是字段公开:

 public class A { private static final int INT_VALUE = 1000; private static final String STRING_VALUE = "foo"; public static int getIntValue() { return INT_VALUE; } public static String getStringValue() { return STRING_VALUE; } } 

不要忘记,在某些情况下,内联对于使用值是必不可less的 – 例如,如果要在开关块中使用INT_VALUE作为例子,则必须将其指定为常量值。

要停止内联,您需要使值非编译时间常量(JLS术语)。 您可以在不使用函数的情况下通过在初始化expression式中使用null来创build最小字节码。

 public static final int INT_VALUE = null!=null?0: 1000; 

尽pipe它在代码生成过程中是非常直接的,但是javac应该优化它,以便在静态初始化程序中将静态字段存储到静态字段中,然后将其存储到一个立即数中。

JLS 13.4.9处理这个问题。 他们的build议是基本上避免编译时常量,如果价值以任何方式可能改变。

(要求内联常量的一个原因是switch语句在每种情况下都需要常量,并且没有两个这样的常量值可能是相同的。编译器在编译时检查switch语句中的重复常量值;类文件格式不做案例价值的符号联系。)

避免广泛分布代码中“不定常量”问题的最好办法是将编译时间常量声明为只有真正不太可能改变的值。 除了真正的math常量之外,我们build议源代码非常节省地使用声明为静态和最终的类variables。 如果需要final的只读属性,更好的select是声明一个私有静态variables和一个合适的访问器方法来获取它的值。 因此我们build议:

 private static int N; public static int getN() { return N; } 

而不是:

 public static final int N = ...; 

没有问题:

 public static int N = ...; 

如果N不必是只读的。

jmake是一个开源项目,声称完成跟踪Java文件之间的依赖关系,并逐步编译所需的最小文件集。 它声称要正确处理静态最终常量的变化,尽pipe有时需要重新编译整个项目。 它甚至可以处理比classfiles更精细的更改; 如果(例如)方法Cm()的签名改变,那么它只重新编译实际依赖于m()的类,而不是所有使用C的类。

免责声明:我没有使用jmake的经验。

我最近遇到了一个类似的问题 ,正如上面所说,这种内联可以用非编译时expression式来解决,比如说:

 public final class A { public static final int INT_VALUE = constOf(1000); public static final String STRING_VALUE = constOf("foo"); } 

constOf方法族仅仅是:

 // @formatter:off public static boolean constOf(final boolean value) { return value; } public static byte constOf(final byte value) { return value; } public static short constOf(final short value) { return value; } public static int constOf(final int value) { return value; } public static long constOf(final long value) { return value; } public static float constOf(final float value) { return value; } public static double constOf(final double value) { return value; } public static char constOf(final char value) { return value; } public static <T> T constOf(final T value) { return value; } // @formatter:on 

这比其他build议像Integer.valueOf(1000).intValue()null!=null?0: 1000

我认为这是一个严重的错误。 Java不是C / C ++。 有一个原则(或不)“编译一次,到处跑”。

在这种情况下,当A类改变时。 任何引用A.CONST_VALUE的类都必须重新编译,他们几乎不知道类A是否被更改。

我觉得Java紧紧依赖于dynamic编译,并没有像C ++那样做任何奇特的编译逻辑。

你可以尝试一些JIT编译器的选项,它可以运行时优化,可能有一些选项来禁用/启用它。

在默认的javac中,您可能无法获得该选项。 你必须使用1.某种types的依赖图像扩展或实现2.使用基于方法的链接。

-s