前面我们已经知道 String 的 switch-case 实现原理 依据 case 值的稀疏程度,分别由两个指令 - tableswitch 和 lookupswitch 实现,但是这两个指令都支持整型, 如何让 String 类型的值 也支持 String 的 switch-case 实现原理
public class Test {
public int switchInt(String name) {
int result;
switch (name) {
case "Java":
result = 100;
break;
case "Go":
result = 200;
break;
case "Kotlin":
result = 300;
break;
default:
result = -1;
}
return result;
}
}
javac
public class Test {
public Test() {
}
public int switchInt(String var1) {
byte var4 = -1;
switch(var1.hashCode()) {
case -2041707231:
if (var1.equals("Kotlin")) {
var4 = 2;
}
break;
case 2312:
if (var1.equals("Go")) {
var4 = 1;
}
break;
case 2301506:
if (var1.equals("Java")) {
var4 = 0;
}
}
byte var2;
switch(var4) {
case 0:
var2 = 10;
break;
case 1:
var2 = 20;
break;
case 2:
var2 = 30;
break;
default:
var2 = 40;
}
return var2;
}
}
字节码
C:\>javac Test.java
C:\>javap -c -p Test.class
Compiled from "Test.java"
public class com.yxzapp.Test {
public com.yxzapp.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int switchInt(java.lang.String);
Code:
0: aload_1 // 使用iload加载局部变量第1个变量加载栈上
1: astore_3 // 将栈顶数据存储到局部变量下标为3的位置
2: iconst_m1 // 将 int 类型值 -1 压栈到栈顶
3: istore 4 // 将栈顶元素存储到局部变量第四个位置
5: aload_3 // 加载局部变量第3个变量加载栈上
6: invokevirtual #2 // Method java/lang/String.hashCode:()I
9: lookupswitch { // 3 // 调用 hashcode 方法, 得到一个整型值。 因为哈希值一般比较离散,所以没有选用tablesw itch,采用lookupswitch 作为实现
-2041707231: 74 // 对应 Kotlin
2312: 59 // 对应 Go
2301506: 44 // 对应 Java
default: 86
}
44: aload_3
45: ldc #3 // String Java
47: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z // 执行equals 方法
50: ifeq 86 // 判断是否相等
53: iconst_0 // 将 int 类型值1压栈到栈顶
54: istore 4 // 将栈顶int的数据存储到局部变量表的第四个
位置
56: goto 86 // 跳转到86行继续执行
59: aload_3
60: ldc #5 // String Go
62: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
65: ifeq 86
68: iconst_1
69: istore 4
71: goto 86
74: aload_3
75: ldc #6 // String Kotlin
77: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z // 执行equals 方法
80: ifeq 86
83: iconst_2
84: istore 4
86: iload 4
88: tableswitch { // 0 to 2
0: 116
1: 122
2: 128
default: 134
}
116: bipush 10
118: istore_2
119: goto 137
122: bipush 20
124: istore_2
125: goto 137
128: bipush 30
130: istore_2
131: goto 137
134: bipush 40
136: istore_2
137: iload_2
138: ireturn
}
可以看到 switch 作用在 String 上最终拆分成两个针对整型 switch 语句,具体流程为:
- 做初始化操作,把入参 name 赋值给局部变量表下标为3的变量 , 把初始化局部变量表中位置为4的变量为 -1
0 1 2 4
this 入参name “java” -1 - 调用 “java” 的 hashCode 方法,得到一个整型值。因为哈希值一般比较离散,所以没有选用tableswitch,采用lookupswitch 作为实现
- 如果 hashCode 等于 字符串 “Java” 的 hashCode会跳转到 44 行继续执行,判断是否相等的指令,如果等于 0 则跳转对应字节码处,相等则把 -1 赋值为 0
看到这里可以能会发现, 字符串的 hashCode冲突要怎么样解决
public class Test {
public int switchInt(String name) {
int result;
switch (name) {
case "Aa":
result = 10;
break;
case "BB":
result = 20;
break;
default:
result = 40;
}
return result;
}
}
javac
public class Test {
public Test() {
}
public int switchInt(String var1) {
byte var4 = -1;
switch(var1.hashCode()) {
case 2112:
if (var1.equals("BB")) {
var4 = 1;
} else if (var1.equals("Aa")) {
var4 = 0;
}
default:
byte var2;
switch(var4) {
case 0:
var2 = 10;
break;
case 1:
var2 = 20;
break;
default:
var2 = 40;
}
return var2;
}
}
}
字节码
C:\>javac Test.java
C:\>javap -c -p Test.class
Compiled from "Test.java"
public class com.yxzapp.Test {
public com.yxzapp.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int switchInt(java.lang.String);
Code:
0: aload_1
1: astore_3
2: iconst_m1
3: istore 4
5: aload_3
6: invokevirtual #2 // Method java/lang/String.hashCode:()I
9: lookupswitch { // 1
2112: 28
default: 55
}
28: aload_3
29: ldc #3 // String BB
31: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
34: ifeq 43
37: iconst_1
38: istore 4
40: goto 55
43: aload_3
44: ldc #5 // String Aa
46: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
49: ifeq 55
52: iconst_0
53: istore 4
55: iload 4
57: lookupswitch { // 2
0: 84
1: 90
default: 96
}
84: bipush 10
86: istore_2
87: goto 99
90: bipush 20
92: istore_2
93: goto 99
96: bipush 40
98: istore_2
99: iload_2
100: ireturn
}
可以看到34 行在 hashCode 冲突的情况下,编译器的处理不过是多一次调用字符串 equals 判断相等的比较 。与 BB 不相等的情况, 会继续判断是否等于 Aa