switch-case对String的特殊处理

9/26/2021 JavaJVM

我们在swtich-case的实现原理与优化方案 (opens new window)中说到,switch-case对String的处理是通过String的hashcode来实现的。

那么,如果两个String的hashcode相同会怎么样呢?比如"Aa"和"BB",他们的hashcode都是2112,那么如下函数:

public void test(String a) {
    int b = 10;
    switch(a) {
        case "Aa":
            b = 1;
        break;
        case "BB":
            b = 100;
        break;
        default:
            b = 1000;
        break;
    }
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14

是不是传入"Aa"和"BB"都一样呢?不会!

因为如果switch的状态值是String的时候,除了进行hashcode校验,还会使用equals进行值校验,类似于HashMap中的定位逻辑,等价代码如下:

public void test(String a) {
    int b = 10;
    switch(a.hashCode()) { // 这里使用hashcode进行检索
        case 2112: // 转换为hashcode进行检索
            if(a.equals("Aa")) { // hashcode命中了,就是用equals来进行二次定位
                b = 1;
            }else if(a.equals("BB")) {
                b = 100;
            }
        break;
        default:
            b = 1000;
        break;
    }
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

可以看到,switch-case在条件值是String的时候,会使用hashcode作为switch-case的状态值,当hashcode相同的时候,再使用equals来进行二次比较,只有hashcode和equals都相同才说明复合条件,这属于分页思想:粗略定位到精准定位

Tips:并不是只有hashcode相同才使用equals比较,而是不管hashcode是否相同,都会使用equals进行比较。

我们将上述代码编译下,来看字节码:

 public void test(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=2
         0: bipush        10
         2: istore_2
         3: aload_1
         4: astore_3
         5: iconst_m1
         6: istore        4
         8: aload_3
         9: invokevirtual #7                  // Method java/lang/String.hashCode:()I // 这里调用了String的hashCode()
        12: lookupswitch  { // 1
                    2112: 32 // 只有一个2112,说明合并了,32表示执行第32行字节码
                 default: 59 // 否则就执行第59行字节码
            }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

我们接着来看字节码指令(只看注释部分即可),对字节码指令不熟悉可以看这里 (opens new window):

32: aload_3                          
33: ldc           #13                 // String BB // 将BB推送至栈顶
35: invokevirtual #15                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z // 执行equals语句
38: ifeq          47                  // 如果相同(if equals)
41: iconst_1
42: istore        4
44: goto          59
47: aload_3
48: ldc           #19                 // String Aa // jiangAa推送值栈顶
50: invokevirtual #15                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z // 执行equals语句
53: ifeq          59                  // 如果相同
56: iconst_0
57: istore        4
59: iload         4
61: lookupswitch  { // 2
                0: 88
                1: 93
            default: 99
    }
88: iconst_1
89: istore_2
90: goto          103
93: bipush        100
95: istore_2
96: goto          103
99: sipush        1000
102: istore_2
103: return
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

上述代码验证了我们的结论: switch-case条件是String时候,使用String的hashCode来作为状态值,如果相同就合并,内部使用equals进行二次比较。

Last Updated: 1/29/2022, 2:35:56 PM