学习Java的人都知道这样的现象,能够使用switch的地方,就一定可以使用if/else,if/else,…来代替。比如下面switch代码
int a = 10;
switch(a){
case 5:
System.out.println("5");
break;
case 3:
System.out.println("3");
break;
case 1:
System.out.println("1");
break;
default:
System.out.println("default");
break;
}
我们可以使用if/else,if/else,…替换为下面代码:
int a = 10;
if(a == 5){
System.out.println("5");
}else if( a= =3 ){
System.out.println("3");
}else if( a= =1 ){
System.out.println("1");
}else{
System.out.println("default");
}
从逻辑上讲,if/else, if/else if/else,三元运算符,switch都可以只用if代替,但使用不同的语法表达更简洁,在条件比较多的时候,switch从性能上也更高。但是为什么switch的性能会更高呢?这就涉及到条件判断的最底层了。
程序的执行最终是会翻译成一条条指令去执行,CPU有一个指令指示器,指示器指向要执行的指令,CPU根据指示器的指示去执行对应的指令。执行一个指令后,指令指示器会自动指向挨着的下一个指令。但有一些特殊的指令,称为跳转指令,这些指令会修改指令指示器的下一个指向,让CPU跳到一个指定的地方执行。跳转指令有两种,一种是条件跳转,另一种是无条件跳转。条件跳转是指检查某个条件,满足则跳转到指定位置执行,不满足则继续向下执行,无条件跳转则是直接进行跳转,跳转到指定的位置执行。而if else 在底层实际上是转换成这些跳转指令执行的。举个例子:
int x=10;[/align]if(x= =10)
{
System.out.println("程序员");
}
System.out.println("播客");
底层翻译为跳转指令如下:
1:int x=10;
2:条件跳转:如果x= =10,跳转到第4行;
3:无条件跳转:跳转到第7行;
4:{
5: System.out.println("程序员");
6:}
7:System.out.println("播客");
看到这儿你肯定会有一个疑问,为什么这儿还要有一个无条件跳转呢。没有这个无条件跳转可以吗?答案是不可以,如果没有这个无条件跳转,不管条件跳转里面的条件满不满足,大括号里面的“黑马程序员”都会执行的。我们来分析一下假如没有无条件跳转:如果x= =10则跳转到大括号执行。如果x!=10呢,cpu继续执行下一条指令,而下一条指令也就是大括号里面的内容。再来分析一下有无条件跳转的情况:如果x= =0则会把无条件跳转的这一行指令跳过,直接进入大括号执行。如果x!=10,则继续执行下一条指令,而下一条指令则是无条件跳转,则会跳转到第7行打印传智播客。也有的时候,编译器会翻译成下面的情况,
1:int x=10;
2:条件跳转:如果x!=10,跳转到第6行;
3:{
4: System.out.println("程序员");
5:}
[align=left] 6:System.out.println("播客");
这样就不需要无条件跳转指令,具体怎么翻译和编译期的实现有关,在单一if的情况下可能不用无条件跳转指令,但稍微复杂一些的情况都需要,下面我把本文第一段提到的代码使用条件跳转指令翻译一下。
int a = 10;[/align]if(a = = 5){
System.out.println("5");
}else if( a= =3 ){
System.out.println("3");
}else if( a= =1 ){
System.out.println("1");
}else{
System.out.println("default");
}
System.out.println("播客");
翻译如下:
1:int a = 10;
2:条件跳转:如果a= =5,跳转到第4行;
3:无条件跳转:跳转到第7行;
4:{
5: System.out.println("5");
6:}
7:条件跳转:如果a= =3,跳转到第9行;
8:无条件跳转:跳转到第12行;
9:{
10: System.out.println("3");
11:}
12:条件跳转:如果x= =1,跳转到第14行;
13:无条件跳转:跳转到第17行;
14:{
15: System.out.println("1");
16:}
17:条件跳转:如果x!=1&&x!=3&&x!=5,跳转到第19行;
18:无条件跳转:跳转到第22行;
19:{
20: System.out.println("default");
21:}
22: //其他代码
if, if/else, if/else if/else, 三元运算符都会转换为条件跳转和无条件跳转。但switch不太一样。switch的编译是这样的,如果分支比较少,可能会翻译为跳转指令。但如果分支比较多,使用条件跳转就会进行很多次的比较运算,效率比较低,这时候编译器会使用一种更为高效的方式,叫跳转表。跳转表是一个映射表,存储了case的值以及要跳转到的地址,形如:
跳转表为什么会更为高效呢?因为,其中的值必须为整数,且按大小顺序排序。(switch值的类型可以是byte,short, int, char, 枚举和String。其中byte/short/int本来就是整数,char本质上也是整数,而枚举类型也有对应的整数,String用于switch时也会转换为整数)按大小排序的整数可以使用高效的二分查找。程序源代码中的case值排列不要求是排序的,编译器会自动排序。如果值是连续的,则跳转表还会进行特殊优化,优化为一个数组,连找都不用找了,值就是数组的下标索引,直接根据值就可以找到跳转的地址。即使值不是连续的,但数字比较密集,差的不多,编译器也可能会优化为一个数组型的跳转表,没有的值指向default分支。这个是可以利用反编译工具进行验证的,举例如下:
上面是我们编写的一个switch判断的源代码。我们编译后生成class文件,再利用反编译工具把class文件还原成源码。见下图:
这足以说明如果case的值是连续的,或者值不是连续的,但数字比较密集,差的不多,编译器也可能会优化为一个数组型的跳转表,没有的值指向default分支,而此时的跳转表数组是
总结:
一般能够用switch表示的逻辑结构,都可以使用ifelse进行表示。但是如果判断的条件分支比较多的时候,建议多使用switch,因为switch的执行效率高,这源于switch在被编译的时候,其跳转指令会被编译为一个数组,从而使用查表法 二分法查找等高效的方式进行指令的跳转。