我在操作某些字节码时遇到了一个问题,其中java编译器(Java 8)没有内联某个final
String
常量,请参阅下面的示例:
public class MyTest
{
private static final String ENABLED = "Y";
private static final String DISABLED = "N";
private static boolean isEnabled(String key) {
return key.equals("A");
}
private static String getString(String key, String value) {
return key + value;
}
public static void main(String[] args) throws Exception {
String flag = getString("F", isEnabled("A") ? ENABLED : DISABLED);
System.out.println(flag);
String flag2 = getString("F", isEnabled("A") ? ENABLED : DISABLED);
System.out.println(flag2);
}
}
使用javac(1.8.0_101)生成的字节码
public static void main(java.lang.String[]) throws java.lang.Exception;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: ldc #8 // String F
2: ldc #2 // String A
4: invokestatic #9 // Method isEnabled:(Ljava/lang/String;)Z
7: ifeq 16
10: getstatic #10 // Field ENABLED:Ljava/lang/String;
13: goto 19
16: getstatic #11 // Field DISABLED:Ljava/lang/String;
19: invokestatic #12 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
22: astore_1
23: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_1
27: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: ldc #8 // String F
32: ldc #2 // String A
34: invokestatic #9 // Method isEnabled:(Ljava/lang/String;)Z
37: ifeq 46
40: getstatic #10 // Field ENABLED:Ljava/lang/String;
43: goto 49
46: getstatic #11 // Field DISABLED:Ljava/lang/String;
49: invokestatic #12 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
52: astore_2
53: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
56: aload_2
57: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
60: return
您可以看到第二次访问字段ENABLED
和DISABLED
时,编译器没有内联其值(使用ldc
),而是使用{{1}直接访问该字段。使用其他编译器(Java 7,Eclipse)测试它并没有触发相同的行为,并且常量总是内联。
这可以被视为编译器错误,还是根据JLS允许不内联字符串常量?
答案 0 :(得分:10)
是的,规范规定了“内联”行为:
13.1. The Form of a Binary
...
- 醇>
对于作为常量变量(§4.12.4)的字段的引用必须在编译时解析为由常量变量的初始化程序表示的值
V
。如果这样的字段是
,必须永远不要观察该字段的默认初始值(如果不同于static
,那么二进制文件中的代码中不应该存在对该字段的引用,包括声明该字段的类或接口。这样的字段必须总是看似已经初始化(§12.4.2);V
)如果这样的字段是非
static
,那么除了包含该字段的类之外,二进制文件中的代码中不应该存在对该字段的引用。 (它将是一个类而不是一个接口,因为一个接口只有static
个字段。)该类应该有代码在实例创建期间将字段的值设置为V
(§12.5)。
注意,这是如何精确地解决您的情况:“如果这样的字段是static
,那么在二进制文件中的代码中不应该存在对该字段的引用,包括类或接口,声明了字段“。
换句话说,如果遇到编译器不遵守此问题,则会发现编译器错误。
作为附录,查找此信息的起点是:
4.12.4. final Variables
...
常量变量是基本类型或类型
final
的{{1}}变量,用常量表达式(§15.28)初始化。变量是否是常量变量可能会影响类初始化(§12.4.1),二进制兼容性(§13.1,§13.4.9)和明确赋值(§16 (Definite Assignment) )。