JLS 是否需要内联最终字符串常量?
Does the JLS require inlining of final String constants?
我 运行 在处理一些字节码时遇到问题,其中某个 final
String
常量未被 java 编译器内联(Java 8), 见下例:
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
),而是使用getstatic
直接访问字段。使用其他编译器(Java 7,Eclipse)对其进行测试并没有触发相同的行为,常量总是内联的。
这可以被认为是编译器错误,还是允许 不 根据 JLS 始终内联字符串常量?
是的,“内联”行为是规范强制要求的:
13.1. The Form of a Binary
…
A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value V
denoted by the constant variable's initializer.
If such a field is static
, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field. Such a field must always appear to have been initialized (§12.4.2); the default initial value for the field (if different than V
) must never be observed.
If such a field is non-static
, then no reference to the field should be present in the code in a binary file, except in the class containing the field. (It will be a class rather than an interface, since an interface has only static
fields.) The class should have code to set the field's value to V
during instance creation (§12.5).
请注意,这如何精确地解决您的情况:“如果这样的字段是 static
,那么二进制文件中的代码中不应存在对该字段的引用,包括class 或声明字段的接口”。
换句话说,如果你遇到一个不遵守这个的编译器,你发现了一个编译器错误。
作为补充,查找此信息的起点是:
4.12.4. final Variables
…
A constant variable is a final
variable of primitive type or type String
that is initialized with a constant expression (§15.28). Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1, §13.4.9), and definite assignment (§16 (Definite Assignment)).
我 运行 在处理一些字节码时遇到问题,其中某个 final
String
常量未被 java 编译器内联(Java 8), 见下例:
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
),而是使用getstatic
直接访问字段。使用其他编译器(Java 7,Eclipse)对其进行测试并没有触发相同的行为,常量总是内联的。
这可以被认为是编译器错误,还是允许 不 根据 JLS 始终内联字符串常量?
是的,“内联”行为是规范强制要求的:
13.1. The Form of a Binary
…
A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value
V
denoted by the constant variable's initializer.If such a field is
static
, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field. Such a field must always appear to have been initialized (§12.4.2); the default initial value for the field (if different thanV
) must never be observed.If such a field is non-
static
, then no reference to the field should be present in the code in a binary file, except in the class containing the field. (It will be a class rather than an interface, since an interface has onlystatic
fields.) The class should have code to set the field's value toV
during instance creation (§12.5).
请注意,这如何精确地解决您的情况:“如果这样的字段是 static
,那么二进制文件中的代码中不应存在对该字段的引用,包括class 或声明字段的接口”。
换句话说,如果你遇到一个不遵守这个的编译器,你发现了一个编译器错误。
作为补充,查找此信息的起点是:
4.12.4. final Variables
…
A constant variable is a
final
variable of primitive type or typeString
that is initialized with a constant expression (§15.28). Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1, §13.4.9), and definite assignment (§16 (Definite Assignment)).