是否可以禁用javac的静态最终变量内联?

时间:2010-08-19 16:53:09

标签: java dependencies bytecode 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 个答案:

答案 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