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的这种内联行为?对于后台,我们正在考虑进行字节码分析以实现依赖性目的,并且它是少数情况下字节码分析无法检测编译时依赖性的情况之一。谢谢!
编辑:这是一个棘手的问题,因为通常我们不控制所有源(例如,定义常量的第三方库)。我们有兴趣从使用常量的角度检测这些依赖关系。由于引用是从使用常量的代码中删除的,因此没有简单的方法来检测它们,缺少源代码分析。
答案 0 :(得分:43)
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();
}
当然,如果您无法访问定义常量的代码,那么这一切都无关紧要。
答案 1 :(得分:13)
我不相信。最简单的解决方法是将这些公开为属性而不是字段:
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
作为案例,则具有被指定为常量值。
答案 2 :(得分:9)
要停止内联,您需要使值为非编译时常量(JLS术语)。您可以在不使用函数的情况下执行此操作,并使用初始化表达式中的null
创建最少的字节码。
public static final int INT_VALUE = null!=null?0: 1000;
虽然它在代码生成中非常直观,但javac
应该优化它是推送一个立即整数,然后存储到静态初始化器中的静态字段。
答案 3 :(得分:7)
JLS 13.4.9处理此问题。如果值以任何可能的方式发生变化,他们的建议是基本上避免编译时常量。
(要求内联的一个原因 常量是switch语句 需要每个案例的常量,没有 两个这样的常数值可能是 相同。编译器检查 在交换机中复制常量值 编译时的声明;班级 文件格式不具有符号效果 案例值的联系。)
避免问题的最佳方法 “不稳定的常数” 广泛分布的代码是要声明的 作为编译时常量只有值 这真的不太可能 更改。除了真实 数学常数,我们建议 源代码非常节省使用 声明的类变量 静态和最终的。如果是只读的 最终的本质是必需的,更好 选择是声明私有静态 变量和合适的访问者 获取其价值的方法。因此我们 建议:
private static int N; public static int getN() { return N; }
而不是:
public static final int N = ...;
没有问题:
public static int N = ...;
如果N不必是只读的。
答案 4 :(得分:1)
我认为这是严重的错误。 Java不是C / C ++。有一个原则(或不是)“编译一次,到处运行”。
在这种情况下,更改A类时。任何引用A.CONST_VALUE的类都必须重新编译,他们几乎不知道是否更改了A类。
答案 5 :(得分:0)
jmake是一个开源项目,声称完成了跟踪Java文件之间的依赖关系并逐步编译所需的最小文件集的全部工作。它声称正确处理静态最终常量的更改,尽管有时需要重新编译整个项目。它甚至可以比类文件更精细地处理更改;如果(例如)方法C.m()的签名发生了变化,那么它只会重新编译实际依赖于m()的类而不是所有使用C的类。
免责声明:我没有使用jmake的经验。
答案 6 :(得分:0)
我最近遇到了类似的issue,并且如上所述,这种内联可以使用非编译时表达式来解决,例如:
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
这比Integer.valueOf(1000).intValue()
或null!=null?0: 1000
答案 7 :(得分:0)
重写A类:
public class A {
public static final int INT_VALUE;
public static final String STRING_VALUE;
static {
INT_VALUE = 1000;
STRING_VALUE = "foo";
}
}
答案 8 :(得分:-2)
我觉得java紧紧依赖于动态编译,它不像C ++那样做任何花哨的编译逻辑。
你可以尝试使用JIT编译器的一些选项来进行运行时优化,这可能有一些选项来禁用/启用它。
在默认的javac中,您可能无法获得该选项。你必须使用 1.某种类型的依赖图,如extends或implements 2.使用基于方法的链接。
-s