一、控制转移指令概述
程序流程离不开条件控制,为了支持条件跳转,虚拟机提供了大量字节码指令,大体上可以分为
- 比较指令、
- 条件跳转指令、
- 比较条件跳转指令、
- 多条件分支跳转指令、
- 无条件跳转指令等。
前面我们也提到过有比较指令,指的是比较两个栈顶元素的大小,并将比较结果入栈
比较指令有: dcmpg,dcmpl、fcmpg、fcmpl、lcmp
与前面讲解的指令类似,首字符d表示double类型,f表示float,l表示long
对于double和float类型的数字,由于NaN的存在,各有两个版本的比较指令
以float为例有fcmpg和fcmpl两个指令,区别在于在数字比较时若遇到NaN值处理结果不同
指令dcmpl和dcmpg也是类似的,根据其命名可以推测其含义,在此不再赘述
指令lcmp针对long型整数,由于long型整数没有NaN值,故无需准备两套指令
举例比较
================================
指令fcmpg和fcmpl都从栈中弹出两个操作数,并将它们做比较
设栈顶的元素为v2,核顶顺位第2位的元素为v1
若v1=v2则压入0
若v1>v2则压入1
若v1<v2则压入1
两个指令的不同之处在于,如果遇到NaN值,fcmpg会压入1,而fcmpl会压入-1
二、控制转移指令的条件跳转指令讲解
条件跳转指令通常和比较指令结合使用。
在条件跳转指令执行前,一般可以先用比较指令进行栈顶元素的准备,然后进行条件跳转
条件跳转指令有: ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull
这些指令都接收两个字节的操作数,用于计算跳转的位置(16位符号整数作为当前位置的offset)
它们的统一含义为:弹出栈顶元素测试它是否满足某一条件,如果满足条件,则跳转到给定位置
具体指令说明如下图所示:
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/94ec4d9b724897348d412f0eab4c73ad.png)
注意:与前面运算规则一致
对于boolean、byte、char、short类型的条件分支比较操作,都是使用int类型的比较指令完成
对于long、 float、double类型的条件分支比较操作,则会先执行相应类型的比较运算指令,运算指令会返回一个整型值到操作数栈中,随后再执行int类型的条件分支比较操作来完成整个分支跳转
由于比较类型最终都会转为int类型,所以对于int类型的条件分支指令是最为丰富和强大的。
接下来我们使用示例代码来体会体会条件跳转指令是怎么样的?
public class IfSwitchGotoTest {
//1.条件跳转指令
public void compare1(){
int a = 0;
if(a == 0){
a = 10;
}else{
a = 20;
}
}
}
接下来我们编译代码使用插件查看具体的字节码是怎么样的?
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/f8ca6c2faeab46913023b6f26eb7b98e.png)
接下来我们分析一下字节码指令,看看具体做了哪些事情?
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/387ead2593a79a9645b0543a3854164a.png)
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/c86626ab79756f30c6fe383bf2cc911f.png)
接下来我们再使用示例代码来体会体会条件跳转指令是怎么样的?
public class IfSwitchGotoTest {
//1.条件跳转指令
public boolean compareNull(String str){
if( str == null){
return tlue;
}else{
return false;
}
}
}
接下来我们编译代码使用插件查看具体的字节码是怎么样的?
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/5cdfccbaf8155a0a8e25c30d09c6df31.png)
接下来我们下一个示例代码来体会不同的条件跳转指令是怎么样的?
public class IfSwitchGotoTest {
//结合比较指令
public void compare2() {
float f1 = 9;
f1oat f2 = 10;
system.out.print1n(f1 < f2);
}
}
接下来我们编译代码使用插件查看具体的字节码是怎么样的?
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/93e154eb7835baaa0a8007892bb22915.png)
接下来我们分析一下字节码指令,看看具体做了哪些事情?
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/833b0f1db19ddc9cff20ac4d53a8d948.png)
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/d9dfc54d85e8753b379a2f42352a6b67.png)
三、控制转移指令的比较条件跳转指令讲解
比较条件跳转指令类似于比较指令和条件跳转指令的结合体,它将比较和跳转两个步骤合二为一
这类指令有: if_icmpeq、if_icmpne、if_icmpdt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq、if_acmpne
其中指令助记符加上“if_”后,以字符“i”开头的指令针对int型整数操作(也包括short和byte类型),以字符“a”开头的指令表示对象引用的比较。
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/b28df63c3b04c4c3ad4a809388402bf4.png)
这些指令都接收两个字节的操作数作为参数,用于计算跳转的位置。
同时在执行指令时
,栈顶需要准备两个元素
进行比较。指令执行完成后
,栈顶的这两个元素被清空,且没有任何数据入栈
。如果预没条件成立,则执行跳转
,否则,继续执行下一条语句。
接下来我们再使用示例代码来体会体会比较条件跳转指令是怎么样的?
public class IfSwitchGotoTest {
public void ifCompare3(){
Object obj1 = new Object();
Object obj2 = new Object();
system.out.println(obj1 == obj2);
system.out.println(obj1 != obj2);
}
}
接下来我们编译代码使用插件查看具体的字节码是怎么样的?
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/5c00b307b4464e3922884e64421911b0.png)
四、控制转移指令的多条件分支跳转指令讲解
多条件分支跳转指令是专为switch-case语句设计的,主要有tableswitch和lookupswitch。
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/55e3640ec3f5e7c491dede2250c9c10a.png)
从助记符上看,两者都是switch语句的实现,它们的区别:
tableswitch指令要求多个条件分支值是连续的,它内部只存放起始值和终止值,以及若干个跳转偏移量,通过给定的操作数index,可以立即定位到跳转偏移量位置,因此效率比较高
lookupswitch指令内部存放着各个离散的case-offset对,每次执行都要搜索全部的case-offset对,找到匹配的case值
,并根据对应的offset计算跳转地址,因此效率较低
接下来我们针对于多条件分支跳转指令的基本测试,请看以下示例代码
//3.多条件分支跳转
public void swtich1(int select){
int num;
switch(select){
case 1:
num = 10;
break;
case 2:
num = 20;
break;
case3:
num = 30;
break;
default:
num = 40;
}
}
接下来我们编译该代码,看看swtich1方法的字节码是什么情况?
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/ff95a0c940afb057bb22ae3c5310e55f.png)
假如我们的switch分支里的case并没有break结束的话,会是什么情况呢?看看示例代码
public void swtich1(int select){
int num;
switch(select){
case 1:
num = 10;
break;
case 2:
num = 20;
case3:
num = 30;
break;
default:
num = 40;
}
}
接下来我们编译该代码,看看swtich1方法的字节码是什么情况?
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/8986f7c05bce87cb09e8501c3bb0d788.png)
接下来我们使用下一个示例代码来体会多条件分支跳转指令,请看以下示例代码
public void swtich2(int select){
int num;
switch(select){
case 100:
num = 10;
break;
case 500:
num = 20;
break;
case 208:
num = 30;
break;
default:
num = 40;
}
}
接下来我们编译该代码,看看swtich2方法的字节码是什么情况?
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/bc2e34709dd7c9ed0851e16a7e6a1fe0.png)
我们知道在JDK7新特性是String类型,接下来使用我们看看String类型的switch是怎么样
//jdk7新特性;引入string类型
public void swtich3(String season){
switch(season){
case "SPRING" : break;
case "SUMMER" : break;
case "AUTUMN" : break;
case "WINTER" : break;
}
}
接下来我们编译该代码,看看swtich2方法的字节码是什么情况?
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/b6eec2af3ea55c95d59dc7e17babee71.png)
五、控制转移指令的无条件跳转指令讲解
目前主要的无条件跳转指令为:goto
指令goto接收两个字节的操作数,共同组成一个带符号的整数,用于指定指令的偏移量,指令执行的目的就是跳转到偏移量给定的位置处
如果指令偏移量太大,超过双字节的带符号整数的范围,则可以使用指令goto_w
,它和goto有相同的作用,但是它接收4个字节的操作数,可以表示更大的地址范围
指令jsr、jsr_w、ret虽然也是无条件跳转的,但主要用于 try-finally语句,且已经被虚拟机逐渐废弃,故不在这里介绍这两个指令
![image.png image.png](https://i-blog.csdnimg.cn/blog_migrate/a87ebb3f6404b09c657bf8d8a031649b.png)