循环语句的实现
循环语句的实现
Java语言提供的循环语句包括for、while和do-while,由于do-while不常用,因此本章不做介绍。Java循环语句的底层字节码实现实际上与条件分支语句的实现差不多,都是通过条件跳转指令完成。
while循环
我们通过一个简单的while循环例子,了解while循环在字节码层面的实现。如代码清单3-55所示。
代码清单3-55 简单while循环
public void whileDemo() { int count = 10; while (count > 0) { count--; } }
使用javap命令输出whileDemo方法的字节码如下。
public void whileDemo(); Code: 0: bipush 10 2: istore_1 3: iload_1 4: ifle 13 7: iinc 1, -1 10: goto 3 13: return
偏移量为0的字节码指令为bipush,该指令将立即数10放到操作数栈顶,接着使用istore_1指令将操作数栈栈顶的10存储到局部变量表索引为1的Slot,也就是给局部变量count赋值。虽然只有一个局部变量,但因为索引为0的Slot用来存储this引用了,所以局部变量count存储在局部变量表的索引为1的Slot。
偏移量为3到10的字节码指令实现while循环。iload_1将局部变量count的值放到操作数栈栈顶,接着使用ifle条件跳转指令判断栈顶的元素是否小于等于0,如果小于等于0则跳转到偏移量为13的字节码指令,也就是结束while循环。ifle后面跟的是while循环体中的代码,iinc指令是将局部变量count减1。while循环体结束处会加上一条goto指令,goto指令是无条件跳转指令,本例中用于跳转到偏移量为3的字节码指令,直到ifle指令的条件成立才跳转到return指令结束循环。
for循环
for循环语句的一般表达式为:
for循环语句的一般表达式为: for(单次表达式;条件表达式;末尾循环体){ 中间循环体; }
我们通过一个简单的for循环例子,了解for循环在字节码层面的实现。如代码清单3-56所示。
代码清单3-56 一般for循环
public int forDemo() { int count = 0; for (int i = 1; i <= 10; i++) { count += i; } return count; }
使用javap命令输出forDemo方法的字节码如下。
public int forDemo(); Code: 0: iconst_0 1: istore_1 2: iconst_1 3: istore_2 4: iload_2 5: bipush 10 7: if_icmpgt 20 10: iload_1 11: iload_2 12: iadd 13: istore_1 14: iinc 2, 1 17: goto 4 20: iload_1 21: ireturn
其中,偏移量为0、1的两条字节码指令实现为局部变量count赋值为0;偏移量为2、3的两条字节码指令实现为局部变量i赋值;偏移量为4、5、7的字节码指令判断局部变量i是否大于10,条件成立则跳转到偏移量为20的字节码指令执行;偏移量为10、11、12、13这四条字节码指令为局部变量count的值加1;偏移量为13的字节码指令给局部变量i的值加1;偏移量为17的字节码指令告诉虚拟机下一条指令的偏移量为4,即跳转到偏移量为4的字节码指令,而偏移量为4开始的连续3条指令就是判断局部变量i是否大于10的,这便是for循环的实现。
我们常用的for循环还有一种,就是forEach。forEach语句是简化版的for语句,实际上是通过编译器实现的。forEach语句的格式为:
for(元素类型 元素变量:数组或集合对象){ }
以使用forEach语句遍历一个集合为例,从字节码层面看forEach的实现。如代码清单3-57所示。
代码清单3-57 forEach遍历结合
public void forEachDemo(List<String> list) { for (String str : list) { System.out.println(str); } }
使用javap命令输出forEachDemo方法的字节码如下。
public void forEachDemo(java.util.List<java.lang.String>); Code: 0: aload_1 1: invokeinterface #2, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 6: astore_2 7: aload_2 8: invokeinterface #3, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 13: ifeq 36 16: aload_2 17: invokeinterface #4, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 22: checkcast #5 // class java/lang/String 25: astore_3 26: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 29: aload_3 30: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 33: goto 7 36: return
从编译后的字节码可以看出,使用forEach语句遍历一个集合,实际上是通过迭代器Iterator加while循环实现的。偏移量为0和1的字节码指令是调用局部变量list的iterator方法获取该集合的迭代器;偏移量为7和8的字节码指令调用迭代器的hasNext方法;接着偏移量为13的字节码指令ifeq判断hasNext方法的返回值是否等于0,如果hasNext方法返回false那么等式就成立,等式成立则跳转到偏移量为36的字节码指令,循环结束。
forEach语句中定义的局部变量str,是在循环体中通过调用迭代器Iterator的next方法为其赋值,为str赋值的字节码指令一定是放在我们编写的forEach循环体内的代码编译后的字节码指令之前。