【问题标题】:Is Java guaranteed to inline string constants if they can be determined at compile time如果可以在编译时确定,Java 是否保证内联字符串常量
【发布时间】:2010-11-27 06:09:09
【问题描述】:

考虑这种情况:

public Class1 {
   public static final String ONE = "ABC";
   public static final String TWO = "DEF";
}

public Class2 {

  public void someMethod() {
    System.out.println(Class1.ONE + Class1.TWO);
  }
}

通常您希望编译器内联 ONE 和 TWO 常量。但是,这种行为是否得到保证?您能否在运行时部署 Class2 而在类路径中没有 Class1,并期望它不管编译器如何都能工作,或者这是一个可选的编译器优化?

编辑:到底为什么要这样做?好吧,我有一个常量,它将在应用程序的两端(通过 RMI 的客户端和服务器)之间共享,在这种特殊情况下,将常量放在只能位于该分隔线一侧的类上会非常方便(因为它在逻辑上是拥有该常量值的那个)而不是仅仅因为它需要由代码的双方共享而将它放在任意常量类中。在编译时它是一组源文件,但在构建时它是按包划分的。

【问题讨论】:

    标签: java constants inline


    【解决方案1】:

    保证被视为常量表达式,保证被section 15.28 of the JLS实习:

    编译时常量表达式是 表示值的表达式 原始类型或字符串 不突然完成而组成 仅使用以下内容:

    • 原始类型的文字和字符串类型的文字(第 3.10.5 节)
    • 转换为基本类型和转换为字符串类型
    • 一元运算符 +、-、~ 和 ! (但不是 ++ 或 --)
    • 乘法运算符 *、/ 和 %
    • 加法运算符 + 和 -
    • ...

    ...

    String 类型的编译时常量 总是“被拘留”以便分享 唯一实例,使用方法 String.intern.

    现在,这并不能完全保证它是内联的。但是,规范的第 13.1 节说:

    对常量字段的引用 变量(§4.12.4)在 编译时间为常数值 表示。没有提及此类 一个常量字段应该存在于 二进制文件中的代码(除了 包含的类或接口 常量字段,将有代码 初始化它),以及这样的常数 字段必须始终显示为 初始化;默认初始值 对于此类字段的类型必须 永远不会被观察到。

    换句话说,即使表达式本身不是常量,也不应该引用Class1。所以,是的,你没事。这并不必然保证在字节码中使用了连接值,但前面引用的位保证了连接值是内部的,所以我会非常感到惊讶如果它不只是内联连接的值。即使没有,您也可以保证它在没有Class1 的情况下也能正常工作。

    【讨论】:

    • 谢谢乔恩,但这并不是我的意思。 “interned”表示只有一个实例,但如果编译器实际上没有内联该类,则如果该类在运行时丢失,您仍然可能会收到 NoClassDefError。
    • 也许我是盲人,但它在哪里这么说?
    • @Yishai:我相信为了保证它被实习,它也必须被内联......不过我会看看我是否能找到一个完整的保证。将编辑答案。
    • JLS 的第 13.1 节保证对常量变量进行内联:“对作为常量变量的字段(第 4.12.4 节)的引用在编译时解析为表示的常量值。没有引用这样的常量字段应该出现在二进制文件的代码中(除了在包含常量字段的类或接口中,它们将具有初始化它的代码),并且此类常量字段必须始终看起来已经初始化;默认初始值绝对不能观察到此类字段的类型。有关讨论,请参见第 13.4.8 节。”
    • @bkail:谢谢——这绝对足以保证代码没问题,即使值没有内联。已编辑我的答案。
    【解决方案2】:

    看起来您正在编写您自己的 enum 内置功能版本,它为您执行 public static final,通过 name()toString() 正确命名(以及具有其他一些优点,但可能有更大的内存占用的缺点)。

    您使用的是不包含枚举的旧版 Java 吗?

    【讨论】:

    • 我的用例表示上下文根和 url,所以它是一个字符串。制作常量的目的是避免在它发生变化时重复它。因此,在这种情况下,枚举将是矫枉过正。我使用的是 Java 1.5,所以我确实有 Enums 并使用它们(可能不够频繁)。
    【解决方案3】:

    使用 javac 1.6.0_14 编译生成以下字节码:

    public void someMethod();
      Code:
       0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
       3:   ldc     #3; //String ABCDEF
       5:   invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8:   return
    

    因此在编译时将字符串连接起来,并将结果包含在 Class2 的常量池中。

    【讨论】:

    • +1,感谢您的研究,但我确实知道编译器的行为方式是这样的。我的问题是规范是否保证了这一点,或者这是否是可选行为(因此以后可能会回来咬人)。
    【解决方案4】:

    JLS 13.4.9。虽然它没有明确要求编译器内联常量,但它暗示条件编译和对switch 语句中的常量的支持会导致编译器始终内联常量。

    【讨论】:

      【解决方案5】:

      我怀疑,但不确定,这会奏效,但这听起来不是一个好主意。

      执行此操作的“正常”方法是:

      1. 将常量放在客户端和服务器之间共享的包中。想必有这样的包,因为那是接口的所在。
      2. 如果没有这样的包,使用共享常量创建 2 个类:一个用于服务器,一个用于客户端。

      【讨论】:

        【解决方案6】:

        它不会由编译器内联,而是由解释器在运行时进行内联,并在可能的情况下转换为汇编代码。

        无法保证,因为并非所有解释器(JVM)都以相同的方式工作。但是最重​​要的实现会做。

        不幸的是,我没有链接来维持这个:(

        【讨论】:

          猜你喜欢
          • 2020-09-28
          • 1970-01-01
          • 1970-01-01
          • 2012-01-24
          • 1970-01-01
          • 2017-01-16
          • 1970-01-01
          • 2012-08-09
          • 1970-01-01
          相关资源
          最近更新 更多