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