尚硅谷2020最新版宋红康JVM教程-中篇-第2章Class文件结构-8-控制转移指令

    科技2022-07-16  121

    引言

    程序流程离不开条件控制,为了支持条件跳转,虚拟机提供了大量字节码指令,大体上可以分为1)比较指令、2)条件跳转指令、3)比较条件转指令、4)多条件分支跳转指令、5)无条件跳转指令等。数值类型的数据,才可以谈大小!(byte\short\char\int;long\float\double)boolean、引用数据类型不能比较大小。

    条件跳转指令

    条件跳转指令通常和比较指令结合使用。在条件跳转指令执行前,一般可以先用比较指令进行栈顶元素的准备,然后进行条件跳转。条件跳转指令有:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonnull。这些指令都接收两个字节的操作数,用于计算跳转的位置(16位符号整数作为当前位置的offset)。它们的统一含义为:弹出栈顶元素,测试它是否满足某一条件,如果满足条件,则跳转到给定位置。

    注意

    与前面运算规则一致: 对于boolean、byte、char、short类型的条件分支比较操作,都是使用int类型的比较指令完成对于long、float、double类型的条件分支比较操作,则会先执行相应类型的比较运算指令,运算指令会返回一个整型值到操作数栈中,随后再执行int类型的条件分支比较操作来完成整个分支跳转 由于各类型的比较最终都会转为int类型的比较操作,所以Java虚拟机提供的int类型的条件分支指令是最为丰富和强

    代码举例

    左边/底部更大压栈进入1

    //1.条件跳转指令 public void compare1() { int a = 0; if (a == 0) { a = 10; } else { a = 20; } } // 0 iconst_0 // 1 istore_1 // 2 iload_1 // 3 ifne 12 (+9) 当不为0时跳转到12 // 6 bipush 10 // 8 istore_1 // 9 goto 15 (+6) //12 bipush 20 //14 istore_1 //15 return // ifnonnull public boolean compareNul1(String str){ if(str==null){ return true; }else { return false; } // 0 aload_1 //1 ifnonnull 6 (+5) // 不为null时跳转到6 //4 iconst_1 //5 ireturn //6 iconst_0 //7 ireturn } //结合比较指令 public void compare2() { float f1 = 9; float f2 = 10; System.out.println(f1 < f2); //true }

    比较条件跳转指令

    比较条件跳转指令类似于比较指令和条件跳转指令的结合体,它将比较和跳转两个步骤合二为一。

    这类指令有:if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne。其中指令助记符加上“if_”后,以字符“i”开头的指令针对int型整数操作(也包括short和byte类型),以字符“a”开头的指令表示对象引用的比较。

    这些指令都接收两个字节的操作数作为参数,用于计算跳转的位置。同时在执行指令时,栈顶需要准备两个元素进行比较。指令执行完成后,栈顶的这两个元素被清空,且没有任何数据入栈。如果预设条件成立,则执行跳转,否则,继续执行下条语句。

    代码举例

    //2.比较条件跳转指令 public void ifCompare1() { int i = 10; int j = 20; System.out.println(i > j); } public void ifCompare2() { short s1 = 9; byte b1 = 10; System.out.println(s1 > b1); // 都是if_ixxx } public void ifCompare3() { Object obj1 = new Object(); Object obj2 = new Object(); // new 指向堆内存地址不一样 System.out.println(obj1 == obj2); //false System.out.println(obj1 != obj2);//true }

    多条件分支跳转

    多条件分支跳转指令是专为switch-case语句设计的,主要有tableswitch和lookupswitch。从助记符上看,两者都是switch语句的实现,它们的区别: tableswitch要求多个条件分支值是连续的,它内部只存放起始值和终止值,以及若干个跳转偏移量,通过给定的操作数index,可以立即定位到跳转偏移量位置,因此效率比较高指令lookupswitch内部存放着各个离散的case-offset对,每次执行都要搜索全部的case-offset对,找到匹配的case值,并根据对应的offset计算跳转地址,因此效率较低。 指令tableswitch的示意图如下图所示。由于tableswitch的case值是连续的,因此只需要记录最低值和最高值,以及每一项对应的offset偏移量,根据给定的index值通过简单的计算即可直接定位到offset。 指令lookupswitch处理的是离散的case值,但是出于效率考虑,将case-offset对按照case值大小排序,给定index时,需要查找与index相等的case,获得其offset,如果找不到则跳转到default。指令lookupswitch如下图所示。

    代码举例

    //3.多条件分支跳转 public void swtichl(int select) { int num; switch (select) { case 1: num = 10; break; case 2: num = 20; // break; case 3: num = 30; break; default: num = 40; } // 1 tableswitch 1 to 3 1: 28 (+27) // 2: 34 (+33) // 3: 37 (+36) // default: 43 (+42) //34 bipush 20 //36 istore_2 //37 bipush 30 //39 istore_2 //40 goto 46 (+6) // 当没有break结束时,继续往下执行break或者default System.out.println(num); } public void swtich2(int select) { int num; switch (select) { case 100: num = 10; break; case 500: num = 20; break; case 200: num = 30; break; default: num = 40; } // 1 lookupswitch 3 // 100: 36 (+35) // 200: 48 (+47) // 500: 42 (+41) // default: 54 (+53) // 自动排序好 } //idk7新特:性引入string类型 public void swtich3(String season) { switch (season) { case "SPRING": break; case "SUMMER": break; case "AUTUMN": break; case "WINTER": break; } // 5 invokevirtual #8 <java/lang/String.hashCode> // 8 lookupswitch 4 // -1842350579: 52 (+44) // -1837878353: 66 (+58) // -1734407483: 94 (+86) // 1941980694: 80 (+72) // default: 105 (+97) // 52 aload_2 // 53 ldc #9 <SPRING> // 55 invokevirtual #10 <java/lang/String.equals> // 对于String,先进行hashcode对比,相同了再equals }

    无条件跳转圈

    目前主要的无条件跳转指令为goto。指令goto接收两个字节的操作数,共同组成一个带符号的整数,用于指定指令的偏移量,指令执行的目的就是跳转到偏移量给定的位置处。如果指令偏移量太大,超过双字节的带符号整数的范围,则可以使用指令goto_w,它和goto有相同的作用,但是它接收4个字节的操作数,可以表示更大的地址范围。指令jsr、jsr_w、ret虽然也是无条件跳转的,但主要用于try-finally语句,且已经被虚拟机逐渐废弃,故不在这里介绍这两个指令。

    代码举例

    //4.无条件桃转指令 public void whileInt() { int i = 0;while (i < 100) { String s = "atguigu.com"; i++; } }

    Processed: 0.014, SQL: 8