18

考虑这种情况:

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 常量。但是,这种行为是否得到保证?您可以在运行时在类路径中部署没有 Class1 的 Class2,并期望它不管编译器如何都能工作,或者这是一个可选的编译器优化?

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

4

6 回答 6

24

它保证被视为常量表达式,并保证被JLS 的第 15.28 节执行

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

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

...

使用 String.intern 方法,String 类型的编译时常量总是“interned”以便共享唯一的实例。

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

对作为常量变量的字段(第 4.12.4 节)的引用在编译时解析为表示的常量值。二进制文件的代码中不应出现对此类常量字段的引用(包含常量字段的类或接口中除外,它们将具有初始化它的代码),并且此类常量字段必须始终显示为已初始化;决不能观察此类字段类型的默认初始值。

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

于 2009-09-10T17:31:57.967 回答
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

所以字符串在编译时被连接起来,结果被包含在 Class2 的常量池中。

于 2009-09-10T18:24:46.750 回答
0

It looks like you're coding your own version of the capability built into enum, which does public static final for you, proper naming via name() and toString() (as well as having some other advantages, but perhaps having the disadvantage of a larger memory footprint).

Are you using an older version of Java that doesn't include enum yet?

于 2009-09-10T19:08:10.740 回答
0

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

于 2009-09-10T18:13:08.697 回答
0

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

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

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

于 2009-09-10T18:04:56.390 回答
0

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

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

  1. 将常量放在客户端和服务器之间共享的包中。据推测,有这样一个包,因为那是接口所在。
  2. 如果没有这样的包,请使用共享常量创建 2 个类:一个用于服务器,一个用于客户端。
于 2009-09-10T18:05:57.403 回答