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,等明天再写,困死了,洗洗睡觉。