以下代码返回给定的String s
是否等于任何其他硬编码的字符串。该方法使用switch
语句来这样做:
public class SwitchOnString {
public static boolean equalsAny(String s) {
switch (s) {
case "string 1":
return true;
case "string 2":
return true;
default:
return false;
}
}
}
根据 Java虚拟机规范(JMS)3.10 Compiling Switches“
switch语句的编译使用 tableswitch 和 lookupswitch 说明。
而且
tableswitch 和 lookupswitch 指令仅对
int
数据起作用。
我阅读了3.10章,但没有发现String
提到的任何地方。
唯一间接接近的一句话是:
其他数字类型必须缩小以键入int才能在开关中使用。
问题1:
在这种情况下,String
还是数字类型吗?还是我错过了什么?
类javap -c
上的SwitchOnString
显示:
Compiled from "SwitchOnString.java"
public class playground.SwitchOnString {
public playground.SwitchOnString();
...
public static boolean equalsAny(java.lang.String);
Code:
0: aload_0
1: dup
2: astore_1
3: invokevirtual #16 // Method java/lang/String.hashCode:()I
6: lookupswitch { // 2
1117855161: 32
1117855162: 44
default: 60
}
...
}
很显然,hashCode
值用作int
的{{1}}键。这可能匹配:
lookupswitch 指令对
case
键(大小写的值) 标签)...
JMS说,继续进行 tableswitch 和 lookupswitch :
tableswitch 指令在开关的情况可以使用时使用 可以有效地表示为目标偏移量表中的索引。 (...) 如果开关的情况很少,则表的表示形式 tableswitch 指令在空间方面变得效率低下。的 可以使用 lookupswitch 指令代替。
如果我做对了,那么情况越稀疏,就越有可能使用 lookupswitch 。
问题2:
但是看一下字节码:
两个字符串大小写稀疏到足以将int
编译为 lookupswitch 吗?还是将switch
上的每个开关都编译为 lookupswitch ?
答案 0 :(得分:1)
规范没有说明如何编译switch
语句,这取决于编译器。
在这方面,JVMS语句“必须将其他数字类型的类型缩小为int
以便在switch
中使用”,这并不表示Java编程语言会进行这种转换,也不是说String
或Enum
是数字类型。即long
,float
和double
是数字类型,但是不支持将它们与Java编程语言中的switch
语句一起使用。>
因此语言规范说,支持switch
上的String
,因此,编译器必须找到一种将其编译为字节码的方法。使用像哈希码这样的不变属性是一种常见的解决方案,但是原则上,也可以使用其他属性,例如长度或任意字符。
如“ Why switch on String compiles into two switches”和“ Java 7 String switch decompiled: unexpected instruction”中所述,javac
当前在switch
值上编译String
时在字节码级别上生成两个切换指令(ECJ还会生成两条指令,但细节可能有所不同。
然后,编译器必须选择一条lookupswitch
或tableswitch
指令。当数字不稀疏时,javac
确实会使用tableswitch
,但前提是该语句具有两个以上的大小写标签。
因此,当我编译以下方法时:
public static char two(String s) {
switch(s) {
case "a": return 'a';
case "b": return 'b';
}
return 0;
}
我明白了
public static char two(java.lang.String);
Code:
0: aload_0
1: astore_1
2: iconst_m1
3: istore_2
4: aload_1
5: invokevirtual #9 // Method java/lang/String.hashCode:()I
8: lookupswitch { // 2
97: 36
98: 50
default: 61
}
36: aload_1
37: ldc #10 // String a
39: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
42: ifeq 61
45: iconst_0
46: istore_2
47: goto 61
50: aload_1
51: ldc #12 // String b
53: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ifeq 61
59: iconst_1
60: istore_2
61: iload_2
62: lookupswitch { // 2
0: 88
1: 91
default: 94
}
88: bipush 97
90: ireturn
91: bipush 98
93: ireturn
94: iconst_0
95: ireturn
但是当我编译时,
public static char three(String s) {
switch(s) {
case "a": return 'a';
case "b": return 'b';
case "c": return 'c';
}
return 0;
}
我明白了
public static char three(java.lang.String);
Code:
0: aload_0
1: astore_1
2: iconst_m1
3: istore_2
4: aload_1
5: invokevirtual #9 // Method java/lang/String.hashCode:()I
8: tableswitch { // 97 to 99
97: 36
98: 50
99: 64
default: 75
}
36: aload_1
37: ldc #10 // String a
39: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
42: ifeq 75
45: iconst_0
46: istore_2
47: goto 75
50: aload_1
51: ldc #12 // String b
53: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ifeq 75
59: iconst_1
60: istore_2
61: goto 75
64: aload_1
65: ldc #13 // String c
67: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
70: ifeq 75
73: iconst_2
74: istore_2
75: iload_2
76: tableswitch { // 0 to 2
0: 104
1: 107
2: 110
default: 113
}
104: bipush 97
106: ireturn
107: bipush 98
109: ireturn
110: bipush 99
112: ireturn
113: iconst_0
114: ireturn
尚不清楚为什么javac
做出此选择。与tableswitch
相比,lookupswitch
的基本占用空间(一个额外的32位字)更高,即使对于两个case
标签的情况,它的字节码仍会更短。
但是可以通过第二条语句显示决策的一致性,第二条语句将始终具有相同的值范围,但是仅取决于标签的数量才能编译为lookupswitch
或tableswitch
。因此,当使用真正的稀疏值时:
public static char three(String s) {
switch(s) {
case "a": return 'a';
case "b": return 'b';
case "": return 0;
}
return 0;
}
它编译为
public static char three(java.lang.String);
Code:
0: aload_0
1: astore_1
2: iconst_m1
3: istore_2
4: aload_1
5: invokevirtual #9 // Method java/lang/String.hashCode:()I
8: lookupswitch { // 3
0: 72
97: 44
98: 58
default: 83
}
44: aload_1
45: ldc #10 // String a
47: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
50: ifeq 83
53: iconst_0
54: istore_2
55: goto 83
58: aload_1
59: ldc #12 // String b
61: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
64: ifeq 83
67: iconst_1
68: istore_2
69: goto 83
72: aload_1
73: ldc #13 // String
75: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
78: ifeq 83
81: iconst_2
82: istore_2
83: iload_2
84: tableswitch { // 0 to 2
0: 112
1: 115
2: 118
default: 120
}
112: bipush 97
114: ireturn
115: bipush 98
117: ireturn
118: iconst_0
119: ireturn
120: iconst_0
121: ireturn
对于稀疏哈希码使用lookupswitch
,对于第二个开关使用tableswitch
。