来源:Java编程的逻辑
程序最终都是一条条的指令,CPU有一个指令指示器,指向下一条要执行的指令,CPU根据指示器的指示加载指令并且执行。指令大部分是具体的操作和运算,在执行这些操作时,执行完一个操作后,指令指示器会自动指向挨着的下一个指令。
但有一些特殊的指令,称为跳转指令,这些指令会修改指令指示器的值,让CPU跳到一个指定的地方执行。跳转有两种,一种是条件跳转,另一种是无条件跳转。条件跳转检查某个条件,满足则进行跳转,无条件跳转则是直接进行跳转。
if, else实际上会转换为这些跳转指令,比如说下面的代码:
1 int a=10;
2 if(a%2==0)
3 {
4 System.out.println("偶数");
5 }
6 //其他代码
转换到的转移指令可能是:
1 int a=10;
2 条件跳转: 如果a%2==0,跳转到第4行
3 无条件跳转:跳转到第7行
4 {
5 System.out.println("偶数");
6 }
7 //其他代码
你可能会奇怪其中的无条件跳转指令,没有它不行吗?不行,没有这条指令,不管什么条件,括号中的代码都会执行。
不过,对应的跳转指令也可能是:
1 int a=10;
2 条件跳转: 如果a%2!=0,跳转到第6行
3 {
4 System.out.println("偶数");
5 }
6 //其他代码
这个就没有无条件跳转指令,具体怎么对应和编译器实现有关。在单一if的情况下可能不用无条件跳转指令,但稍微复杂一些的情况都需要。if, if/else, if/else if/else, 三元运算符都会转换为条件跳转和无条件跳转。但switch不太一样。
switch的转换和具体系统实现有关,如果分支比较少,可能会转换为跳转指令。但如果分支比较多,使用条件跳转会进行很多次的比较运算,效率比较低,可能会使用一种更为高效的方式,叫跳转表。跳转表是一个映射表,存储了可能的值以及要跳转到的地址,形如:
值1 | 代码块1的地址 |
---|---|
值2 | 代码块2的地址 |
… | |
值n | 代码块n的地址 |
跳转表为什么会更为高效呢?因为,其中的值必须为整数,且按大小顺序排序。按大小排序的整数可以使用高效的二分查找,即先与中间的值比,如果小于中间的值则在开始和中间值之间找,否则在中间值和末尾值之间找,每找一次缩小一倍查找范围。如果值是连续的,则跳转表还会进行特殊优化,优化为一个数组,连找都不用找了,值就是数组的下标索引,直接根据值就可以找到跳转的地址。即使值不是连续的,但数字比较密集,差的不多,编译器也可能会优化为一个数组型的跳转表,没有的值指向default分支。
程序源代码中的case值排列不要求是排序的,编译器会自动排序。之前说switch值的类型可以是byte, short, int, char, 枚举和String。其中byte/short/int本来就是整数,char本质上也是整数,而枚举类型也有对应的整数,String用于switch时也会转换为整数(通过hashCode方法)
为什么不可以使用long呢?因为跳转表值的存储空间一般为32位,容纳不下long;跳转表也没必要为long,一般用不上64位,不必浪费空间
和if一样,循环内部也是靠条件转移和无条件转移指令实现的。比如说下面的代码:
int[] arr = {1,2,3,4};
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
其对应的跳转过程可能为:
在if中,跳转只会往后面跳,而for会往前面跳,第6行就是无条件跳转指令,跳转到了前面的第3行。break/continue语句也都会转换为跳转指令。