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

Yis*_*hai 18 java inline constants

考虑这种情况:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

通常,您会期望编译器内联ONE和TWO常量.但是,这种行为有保证吗?你可以在类路径中没有Class1的运行时Class2部署,并期望它无论编译器如何工作,或者这是一个可选的编译器优化?

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

Jon*_*eet 23

它保证被视为常量表达式,并保证由JLS的第15.28节实现:

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

  • 原始类型的文字和String类型的文字(§3.10.5)
  • 转换为基本类型并转换为String类型
  • 一元运算符+, - ,〜,和!(但不是++或 - )
  • 乘法运算符*,/和%
  • 添加剂操作符+和 -
  • ...

...

String类型的编译时常量始终是"实例化",以便使用String.intern方法共享唯一实例.

现在,这并不能说保证内联.但是,规范第13.1节说:

对作为常量变量的字段(第4.12.4节)的引用在编译时被解析为表示的常量值.在二进制文件的代码中不应该存在对这样的常量字段的引用(除了包含常量字段的类或接口,它将具有初始化它的代码),并且这样的常量字段必须总是看起来已经初始化; 绝不能遵守此类字段类型的默认初始值.

换句话说,即使表达式本身不是常量,也不应该参考Class1.所以是的,你没事.这并不一定能保证在字节码中使用连接值,但前面引用的位保证了连接值的实现,所以如果它不只是内联连接值,我会非常惊讶.即使它没有,你也能保证它没有用Class1.

  • JLS的第13.1节保证内联常量变量:"对常量变量字段的引用(第4.12.4节)在编译时被解析为表示的常量值.代码中不应该存在对这样的常量字段的引用在二进制文件中(包含常量字段的类或接口除外,它将具有初始化它的代码),并且此类常量字段必须始终显示为已初始化;此类字段的类型的默认初始值必须永远不会观察.见第13.4.8节的讨论." (9认同)

Esk*_*ola 10

使用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
Run Code Online (Sandbox Code Playgroud)

因此字符串在编译时连接,结果包含在Class2的常量池中.