java-是否可以禁用静态最终变量的javac内联?
Java静态编译器(javac)内联一些静态最终变量,并将值直接带到常量池中。 考虑以下示例。 A类定义了一些常量(公共静态最终变量):
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中。 结果,在编译时必须将类A的依赖项B从字节码中删除。 这是一个相当特殊的行为,因为您在编译时正在烘烤这些常量的值。 而且您会认为这是JIT编译器在运行时可以做的最简单的事情之一。
有什么方法或任何隐藏的编译器选项可让您禁用Javac的这种内联行为吗? 对于背景,我们正在考虑进行字节码分析以实现依赖关系,这是字节码分析无法检测到编译时依赖关系的少数情况之一。 谢谢!
编辑:这是一个令人烦恼的问题,因为通常我们不控制所有源代码(例如,定义常量的第三方库)。 我们有兴趣从使用常量的角度检测这些依赖关系。 由于从使用常量的代码中删除了引用,因此没有简单的方法可以检测到它们,而无需进行源代码分析。
9个解决方案
44 votes
Java Puzzlers(Joshua Bloch)的第93项说,您可以通过防止将最终值视为常量来解决此问题。 例如:
public class A {
public static final int INT_VALUE = Integer.valueOf(1000).intValue();
public static final String STRING_VALUE = "foo".toString();
}
当然,如果您无权访问定义常量的代码,则这些都不相关。
DJClayworth answered -01-13T09:05:05Z
13 votes
我不相信 最简单的解决方法是将这些作为属性而不是字段公开:
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;
}
}
不要忘记,在某些情况下,内联对于使用该值必不可少-例如,如果要在开关块中使用INT_VALUE作为案例,则必须将其指定为恒定值。
Jon Skeet answered -01-13T09:05:29Z
9 votes
要停止内联,您需要使值成为非编译时间常数(JLS术语)。 您可以在不使用函数的情况下执行此操作,而在初始化程序表达式中使用javac可以创建最少的字节码。
public static final int INT_VALUE = null!=null?0: 1000;
尽管它在代码生成中非常真实,但javac应该对其进行优化,以使其为立即数的紧随其后的是存储到静态初始化程序中的静态字段的操作。
Tom Hawtin - tackline answered -01-13T09:05:54Z
7 votes
JLS 13.4.9处理此问题。 他们的建议是,如果该值可能以任何方式改变,则基本上避免编译时常量。
(要求内联的一个原因 常量是switch语句 在每种情况下都需要常量,并且没有 两个这样的常数值可能是 相同。 编译器检查 在开关中复制常量值 在编译时声明; 班级 文件格式不做符号 案例值的链接。)
避免出现问题的最佳方法 “常量” 分布广泛的代码是要声明 作为仅编译时间常数的值 真正不可能的 更改。 除了为真 数学常数,我们建议 源代码非常少用 声明的类变量 静态的和最终的。 如果是只读的 需要最终的性质,更好 选择是声明一个私有静态 变量和合适的访问器 获得价值的方法。 因此,我们 推荐:
private static int N;
public static int getN() { return N; }
而不是:
public static int N = ...;
没问题:
public static int N = ...;
如果N不必为只读。
Mark Peters answered -01-13T09:06:50Z
2 votes
我认为这是一个严重的错误。 Java不是C / C ++。 有一个原则(或没有)“一次编译,随处运行”。
在这种情况下,当更改A类时。 任何引用A.CONST_VALUE的类都必须重新编译,并且它们几乎不知道是否更改了A类。
neoedmund answered -01-13T09:07:14Z
2 votes
像这样重写A类:
public class A {
public static final int INT_VALUE;
public static final String STRING_VALUE;
static {
INT_VALUE = 1000;
STRING_VALUE = "foo";
}
}
rveach answered -01-13T09:07:34Z
1 votes
jmake是一个开源项目,声称可以完成跟踪Java文件之间的依赖关系并逐步编译所需的最少文件集的全部工作。 它声称可以正确处理对静态最终常数的更改,尽管有时需要重新编译整个项目。 它甚至比类文件更精细地处理更改。 如果(例如)方法C.m()的签名发生更改,则它仅重新编译实际上依赖于m()的类,而不是所有使用C的类。
免责声明:我没有使用jmake的经验。
mhagger answered -01-13T09:07:59Z
0 votes
我最近遇到了一个类似的问题,如上所述,可以使用非编译时表达式来解决这种内联问题,例如:
public final class A {
public static final int INT_VALUE = constOf(1000);
public static final String STRING_VALUE = constOf("foo");
}
Integer.valueOf(1000).intValue()方法系列仅是:
// @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 constOf(final T value) { return value; }
// @formatter:on
这比其他建议(例如Integer.valueOf(1000).intValue()或null!=null?0: 1000)短一些
Lyubomyr Shaydariv answered -01-13T09:08:28Z
-2 votes
我觉得Java紧密依赖于动态编译,并且它不像C ++那样执行任何复杂的编译逻辑。
您可以使用JIT编译器尝试一些选项,这些选项可以进行运行时优化,其中可能有一些选项可以禁用/启用此选项。
在默认的javac中,您可能无法获得该选项。 你必须使用1.某种类型的依赖图,例如扩展或实现2.使用基于方法的链接。
-s
kadalamittai answered -01-13T09:09:01Z