注定是难忘的日子

      2019年04月30日,是一个既伤感又开心的日子。

      四月尾巴的杭州,又开始阴沉沉的,厚重的云朵含着丰富的雨水,悬在空中。我和尽哥(孤尽)在家边上的临平水景公园有孚茶书屋,从早上十点聊到了下午四点半。从学习到工作,源码到生活,聊得是酣畅淋漓。想说什么,都说了,想表达的敬佩和遗憾,也都倾诉了,也许,尽哥,能亦师亦友吧。

      其实到了成年人,有些苦头是一定要去经历,也一定要去自己吃过,才知道的。正是这些苦头的存在,让人觉得人生有真实存在感,也正是大大小小的每一个选择,有了现在的人生和未来的可能。有些事,我懂,有些事,我需要去懂,内心怀着感激之情,对于过往的人和事,都有一颗感恩的心,我想我是幸运和幸福的。

      对于尽哥,我一直以来都是钦佩的:一个男人,能受得了委屈,担得起责任,抗得了大旗,这是难能可贵的。有时候,还能细腻如丝,关怀备至,着实让我在冰冷的企业机器中,感受到家的温暖,而不是战场的刀光剑影。

      今天,我们探讨了很多,生活做人方面的事,我想就写到这里,接下去的,想记录下今天从Java switch语句中学到的东西。

工具

  • java
  • javac
  • javap
  • diff

代码

第一段代码

public class Test {
    public static void main(String[] args) {
        switch ("H") {
            case "A":
                break;
            case "xxxx":
                break;
        }
    }
}

Test.class字节码

Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String H
       2: astore_1
       3: iconst_m1
       4: istore_2
       5: aload_1
       6: invokevirtual #3                  // Method java/lang/String.hashCode:()I
       9: lookupswitch  { // 2
                    65: 36
               3694080: 50
               default: 61
          }
      36: aload_1
      37: ldc           #4                  // String A
      39: invokevirtual #5                  // 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           #6                  // String xxxx
      53: invokevirtual #5                  // 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: 91
          }
      88: goto          91
      91: return
}

         可以从第二部分第八行看到,源码中的switch("H")采用了StringhashCode方法。其实switch方法只支持int类型的匹配,其他基本数据类型都是会被语法糖转换为整型来做判断和匹配。

第二段代码

public class Test {
    public static void main(String[] args) {
        A a = A.A;
        System.out.println("B ordinal is :" + A.B.ordinal());
        switch (a) {
            // 请特别注意这里case出现的顺序
            case B:
                System.out.println("This A");
                break;
            case C:
                System.out.println("This B");
                break;
            case A:
                System.out.println("This C");
                break;
            default:
                System.out.println("Default.");
                break;
        }
    }

    enum A {
        A, B, C;
    }
}

         这里使用了一个内部枚举类A,并作为switch中的判断类型。从之前知识来说,这里会将枚举值转换为整型来做条件匹配。所以我们编译一下,发现有三个class文件:Test.java、Test$1.java和Test$A.java。我们知道,Test.class是Test类生成的,Test$A.class是内部枚举类A生成的。但是Test$1.class呢?我们暂且放一下,来看下反编译的结果。

Test.class字节码

Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field Test$A.A:LTest$A;
       3: astore_1
       4: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: new           #4                  // class java/lang/StringBuilder
      10: dup
      11: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      14: ldc           #6                  // String B ordinal is :
      16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: getstatic     #8                  // Field Test$A.B:LTest$A;
      22: invokevirtual #9                  // Method Test$A.ordinal:()I
      25: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      28: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      31: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      34: getstatic     #13                 // Field Test$1.$SwitchMap$Test$A:[I
      37: aload_1
      38: invokevirtual #9                  // Method Test$A.ordinal:()I
      41: iaload
      42: tableswitch   { // 1 to 3
                     1: 68
                     2: 79
                     3: 90
               default: 101
          }
      68: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      71: ldc           #14                 // String This A
      73: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      76: goto          109
      79: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      82: ldc           #15                 // String This B
      84: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      87: goto          109
      90: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      93: ldc           #16                 // String This C
      95: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      98: goto          109
     101: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
     104: ldc           #17                 // String Default.
     106: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     109: return
}

      此处对比第一段代码的反编译结果,需要关注两个点:

  1. 第二部分的第四十二行,switch在这里被编译成tableswitch指令,和之前的lookupswitch不同;
  2. tableswitch中的内容,条件成了顺序的1、2、3;
  3. switch(a),采用的是Test$A.ordinal()方法,也就是说,枚举值转换为整型来做switch判断,采用的是枚举值的ordinal方法;

      (省略Test$A.class字节码,与此处讨论内容关系不大)

Test$1.class字节码

Compiled from "Test.java"
class Test$1 {
  static final int[] $SwitchMap$Test$A;

  static {};
    Code:
       0: invokestatic  #1                  // Method Test$A.values:()[LTest$A;
       3: arraylength
       4: newarray       int
       6: putstatic     #2                  // Field $SwitchMap$Test$A:[I
       9: getstatic     #2                  // Field $SwitchMap$Test$A:[I
      12: getstatic     #3                  // Field Test$A.B:LTest$A;
      15: invokevirtual #4                  // Method Test$A.ordinal:()I
      18: iconst_1
      19: iastore
      20: goto          24
      23: astore_0
      24: getstatic     #2                  // Field $SwitchMap$Test$A:[I
      27: getstatic     #6                  // Field Test$A.C:LTest$A;
      30: invokevirtual #4                  // Method Test$A.ordinal:()I
      33: iconst_2
      34: iastore
      35: goto          39
      38: astore_0
      39: getstatic     #2                  // Field $SwitchMap$Test$A:[I
      42: getstatic     #7                  // Field Test$A.A:LTest$A;
      45: invokevirtual #4                  // Method Test$A.ordinal:()I
      48: iconst_3
      49: iastore
      50: goto          54
      53: astore_0
      54: return
    Exception table:
       from    to  target type
           9    20    23   Class java/lang/NoSuchFieldError
          24    35    38   Class java/lang/NoSuchFieldError
          39    50    53   Class java/lang/NoSuchFieldError
}

      (如果字节码看不清楚,可以使用其他反编译工具,让代码更可读)可以看到这个Test$1.class的字节码,其实是创建了一个SwtichMap的一维数组,下标是枚举类不同值的ordinal值,对应的值是顺序整型:1、2、3,也就是:

+------------------+-----------------+-----------------+
|                  |                 |                 |
|  A.A.ordinal()   |  A.B.ordinal()  |  A.C.ordinal()  |
|                  |                 |                 |
+--------+---------+--------+--------+---------+-------+
         |                  |                  |
         v                  v                  v
    +----+----+        +----+----+       +-----+----+
    |    3    |        |    1    |       |     2    |
    +---------+        +---------+       +----------+

      对照Test.class字节码的三条注意内容中的后两条,可以看到,switch()中会使用枚举值的ordinal方法获取枚举值在枚举类中的位置,而case中使用枚举值,但是条件值其实不是位置数字,而是SwitchMap中对应的值,而不是下标。

      那么,tableswitchlookupswitch指令又有什么不同呢?我们来看下第三段代码:

第三段代码

public class Test {
    public static void main(String[] args) {
        switch (2) {
            case 1:
                break;
            case 2:
                break;
            case 3:
                break;
            case 560:       // 特别注意
                break;
            case 5:
                break;
            case 6:
                break;
            case 7:
                break;
            case 8:
                break;
        }
    }
}

Test.class字节码

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_2
       1: lookupswitch  { // 8
                     1: 76
                     2: 79
                     3: 82
                     5: 88
                     6: 91
                     7: 94
                     8: 97
                   560: 85
               default: 97
          }
      76: goto          97
      79: goto          97
      82: goto          97
      85: goto          97
      88: goto          97
      91: goto          97
      94: goto          97
      97: return
}

      可以看到,这里使用了lookupswitch。再看下第四段代码:

第四段代码

public class Test {
    public static void main(String[] args) {
        switch (2) {
            case 1:
                break;
            case 2:
                break;
            case 3:
                break;
            case 4:
                break;
            case 5:
                break;
            case 6:
                break;
            case 7:
                break;
            case 8:
                break;
        }
    }
}

Test.class字节码

Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_2
       1: tableswitch   { // 1 to 8
                     1: 48
                     2: 51
                     3: 54
                     4: 57
                     5: 60
                     6: 63
                     7: 66
                     8: 69
               default: 69
          }
      48: goto          69
      51: goto          69
      54: goto          69
      57: goto          69
      60: goto          69
      63: goto          69
      66: goto          69
      69: return
}

      这里使用的是tableswitch。看到这里,是不是有感觉了?

      其实使用tableswitch还是lookupswitch,完全是看这个整型条件的值遍历是否快捷,如果是连续的整型值,那么以数组方式存放条件执行语句是合理的,其查询复杂度为O(1)。但是如果条件代表的整型数不是连续的,譬如String常量的hashCode值,又比如出现了560这样的不连续数字。那就无法使用数组下标代替条件值来做映射关系,只能采用有序数组的二分查找方式来提升条件匹配效率,所以有了lookupswitch