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")
采用了String
的hashCode
方法。其实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
}
此处对比第一段代码的反编译结果,需要关注两个点:
-
第二部分的第四十二行,
switch
在这里被编译成tableswitch
指令,和之前的lookupswitch
不同; - tableswitch中的内容,条件成了顺序的1、2、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
中对应的值,而不是下标。
那么,tableswitch
和lookupswitch
指令又有什么不同呢?我们来看下第三段代码:
第三段代码
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
。