如果可以在编译时确定Java,则可以保证Java内联字符串常量
考虑这种情况:
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)之间共享,在这种特殊情况下将常量放在一个只能位于该除法的一侧的类上是非常方便的(因为它在逻辑上是拥有该常量值的那个而不是将它放在任意常量类中,因为它需要由代码的两端共享。 在编译时它的所有一组源文件,但在构建时它被包分开。
它保证被视为一个常量表达式,并保证被JLS的第15.28节所限制 :
编译时常量表达式是表示基本类型值的表达式或不突然完成的字符串,仅使用以下内容组成:
- 原始类型的文字和String类型的文字(§3.10.5)
- 转换为基本类型并转换为String类型
- 一元运算符+, – ,〜,和! (但不是++或 – )
- 乘法运算符*,/和%
- 添加剂操作符+和 –
- …
…
String类型的编译时常量始终是“实例化”,以便使用String.intern方法共享唯一实例。
现在,这并不能说它保证被内联。 但是,规范第13.1节说:
对作为常量变量的字段(第4.12.4节)的引用在编译时被解析为表示的常量值。 在二进制文件的代码中不应该存在对这样的常量字段的引用(除了包含常量字段的类或接口,它将具有初始化它的代码),并且这样的常量字段必须总是看起来已经初始化; 绝不能遵守此类字段类型的默认初始值。
换句话说, 即使表达式本身不是常量 ,也不应该引用Class1
。 所以,是的,你没事。 这并不一定能保证在字节码中使用连接值,但前面引用的位保证连接值是实例化的,所以如果它不只是内联连接值,我会非常惊讶。 即使它没有,你也可以保证它在没有Class1
情况下工作。
使用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的常量池中。
它不会被编译器内联,而是由运行时的解释器内联,如果可能的话转换为汇编代码。
它无法保证,因为并非所有解释器(JVM)都以相同的方式工作。 但最重要的实现方式是可行的。
不幸的是我没有维持这个的链接:(
我怀疑,但不确定,这会起作用,但这听起来不是一个好主意。
“正常”的方法是:
- 将常量放在客户端和服务器之间共享的包中。 据推测,有这样一个包,因为这是接口所在的地方。
- 如果没有这样的包,则使用共享常量创建2个类:一个用于服务器,一个用于客户端。
见JLS 13.4.9 。 虽然它没有明确要求编译器内联常量,但它暗示条件编译和对switch
语句中常量的支持会导致编译器始终内联常量。
看起来你正在编写你自己的enum
内置function版本,它为你做public static final
,通过name()
和toString()
正确命名(以及具有一些其他优点,但可能有缺点更大的内存占用量)。
您使用的是不包含enum的旧版Java吗?