Java是否保证内联字符串常量,如果它们可以在编译时确定

2022-09-02 20:30:14

请考虑以下情况:

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的客户端和服务器)之间共享,在这种特殊情况下,将常量放在只能位于该除法的一侧的类上非常方便(因为它在逻辑上是拥有该常量值的类),而不是仅仅因为它需要由共享而将其放在任意常量类中。代码的两面。在编译时,它都是一组源文件,但在构建时,它被包除以。


答案 1

它保证被视为常量表达式,并保证被JLS的第15.28节所禁存:

编译时常量表达式是表示基元类型值或 String 的表达式,它不会突然完成,并且仅使用以下内容组成:

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

...

String 类型的编译时常量始终被“暂存”,以便使用方法 String.intern 共享唯一实例。

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

对常量变量字段的引用 (§4.12.4) 在编译时解析为所表示的常量值。在二进制文件的代码中不应存在对此类常量字段的引用(包含在包含常量字段的类或接口中除外,该类或接口将具有用于初始化它的代码),并且此类常量字段必须始终显示为已初始化;绝不能遵守此类字段类型的默认初始值。

换句话说,即使表达式本身不是常量,也不应该引用 。所以,是的,你没事。这并不一定保证在字节码中使用串联值,但前面引用的位保证了串联值被暂存,所以如果它不只是内联串联值,我会感到非常惊讶。即使它没有,您也可以保证在没有 .Class1Class1


答案 2

使用 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 的常量池中。


推荐