规范没有告诉如何编译switch
语句,这取决于编译器。
在这方面,JVMS 声明“其他数字类型必须缩小为类型”int
用于switch
” 并没有说 Java 编程语言会做这样的转换,也没有说String
or Enum
是数字类型。 IE。long
, float
and double
are数字类型,但不支持将它们与switch
Java 编程语言中的语句。
So the language规范说switch
over String
支持,因此,编译器必须找到一种方法将它们编译为字节码。使用哈希码等不变属性是一种常见的解决方案,但原则上也可以使用长度或任意字符等其他属性。
正如“为什么 switch on String 编译成两个 switch https://stackoverflow.com/q/25568639/2711488” and “Java 7 String switch 反编译:意外指令 https://stackoverflow.com/q/6956792/2711488”, javac
目前编译时会在字节码级别生成两个 switch 指令switch
over String
值(ECJ 也生成两条指令,但细节可能有所不同)。
然后,编译器必须选择一个lookupswitch https://docs.oracle.com/javase/specs/jvms/se10/html/jvms-6.html#jvms-6.5.lookupswitch or tableswitch https://docs.oracle.com/javase/specs/jvms/se10/html/jvms-6.html#jvms-6.5.tableswitch操作说明。javac
确实使用tableswitch
当数字不稀疏时,但前提是语句具有两个以上的 case 标签。
所以当我编译以下方法时:
public static char two(String s) {
switch(s) {
case "a": return 'a';
case "b": return 'b';
}
return 0;
}
I get
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;
}
I get
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
与相比,具有更高的基本占用空间(一个额外的 32 位字)lookupswitch
,即使对于两个,它的字节码仍然会更短case
标签场景。
但是决策的一致性可以通过第二条语句来显示,该语句始终具有相同的值范围,但编译为lookupswitch
or 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
using lookupswitch
对于稀疏哈希码,但是tableswitch
对于第二个开关。