String 的 switch-case 实现原理

前面我们已经知道 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."":()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 语句,具体流程为:

  1. 做初始化操作,把入参 name 赋值给局部变量表下标为3的变量 , 把初始化局部变量表中位置为4的变量为 -1
    0 1 2 4
    this 入参name “java” -1
  2. 调用 “java” 的 hashCode 方法,得到一个整型值。因为哈希值一般比较离散,所以没有选用tableswitch,采用lookupswitch 作为实现
  3. 如果 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."":()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

你可能感兴趣的:(JVM,java,jvm)