1、問題
對於下面的代碼:
public class Switch{
public void test(int i) {
switch(i) {
case 0:
case 1:
System.out.println("a");
break;
case 2:
System.out.println("a");
break;
default:
break;
}
}
}
我們會發現,當i=0的時候,會進入0的case塊,並且還繼續執行1的case塊;當i=2的時候只執行2的case塊;當i=3的時候直接執行的是default的case塊。很多時候我們不知道這其中的原因是什麼,下面就是筆者帶領大家深入理解這些東西背後的深層次的原因。
2、反編譯上面的代碼:
public class Switch {
public Switch();
Code:
0: aload_0
1: invokespecial #1; // Method java/lang/Object."<init>":()V
4: return
public void test(int);
Code:
0: iload_1
1: tableswitch { // 0 to 4
0: 36
1: 36
2: 58
3: 69
4: 47
default: 69
}
36: getstatic #2; // Field java/lang/System.out:Ljava/io/PrintStream;
39: ldc #3; // String a
41: invokevirtual #4; // Method java/io/PrintStream.println:(Ljava/lang/String;)V
44: goto 69
47: getstatic #2; // Field java/lang/System.out:Ljava/io/PrintStream;
50: ldc #5; // String c
52: invokevirtual #4; // Method java/io/PrintStream.println:(Ljava/lang/String;)V
55: goto 69
58: getstatic #2; // Field java/lang/System.out:Ljava/io/PrintStream;
61: ldc #3; // String a
63: invokevirtual #4; // Method java/io/PrintStream.println:(Ljava/lang/String;)V
66: goto 69
69: return
}
我們只關注test方法,Code後面的都是反編譯出來的字節碼,其中每行的開始是字節碼在每一個代碼塊的開始的字節,所以這些數字不會是連續的,爲了方便,筆者下面只稱這些字節索引值爲行。
0行:將本地方法棧的第一個局部加載到操作棧中,第一個參數就是test的方法的參數i,可能有讀者會問,那test方法的本地方法棧的第0個局部變量是什麼?對於非static的方法,編譯器會給方法的插入一個默認的參數,這個參數指向類的實例,其實也就是所謂的this對象,這就是爲什麼我們在static方法中沒有辦法使用this,而只能在非static方法中使用的緣故。
1行:在表中查找實際的i值對應的實際的代碼入口,從這個代碼查找我們可以發現i=0和i=1的時候指向了相同的代碼入口,i=2時指向了case 2的代碼的入口
36行到69行:我們發現swicth-case語句中的所有的非case語句,都編譯到了一起,只有遇到像44行、55行這樣的break語句纔會跳出,也就是說,如果沒有,就會繼續往下走,所以說,像下面的語句,遇到case A,會執行do_a也會執行do_b。
case A:
do_a
case B:
do_b;
break;
3、還有一些關於tableswitch的其他東西,比如排序等等,還有稀疏的case情況的處理,以及java 7支持的string的swicth-case,等明天再寫,困死了,洗洗睡覺。