首页 > 代码库 > java foreach实现原理
java foreach实现原理
java foreach 语法糖实现原理
一 、 示例代码
1 import java.util.ArrayList; 2 import java.util.List; 3 4 /** 5 * 6 * @author lulei 7 * @date 2014-9-23 8 * 9 */10 public class TestForeach {11 12 private List<String> list = new ArrayList<String>();13 private String[] array = new String[10];14 15 void testCollect() {16 for (String str : list) {17 System.out.println(str);18 }19 }20 21 void testArray() {22 for (String str : array)23 System.out.println(str);24 }25 }
二 、 字节码
void testCollect(); Code: 0: aload_0 1: getfield #4 // Field list:Ljava/util/List; 4: invokeinterface #7, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 9: astore_1 10: aload_1 11: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 16: ifeq 39 19: aload_1 20: invokeinterface #9, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 25: checkcast #5 // class java/lang/String 28: astore_2 29: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream; 32: aload_2 33: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 36: goto 10 39: return void testArray(); Code: 0: aload_0 1: getfield #6 // Field array:[Ljava/lang/String; 4: astore_1 5: aload_1 6: arraylength 7: istore_2 8: iconst_0 9: istore_3 10: iload_3 11: iload_2 12: if_icmpge 34 15: aload_1 16: iload_3 17: aaload 18: astore 4 20: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream; 23: aload 4 25: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 28: iinc 3, 1 31: goto 10 34: return
三、 注释
void testCollect();
可以看到这是在java虚拟机编译时,将集合容器类型的foreach转化成了使用迭代器进行迭代遍历, 源码中我们只是显式的声明一个局部变量(str),但是在经过编译器编译后已经使用到下标为2的slot(分别为this,iterator,str所用)。这是foreach语法糖在经过虚拟机编译时,生成的临时变量。
0: aload_0 // 将 this 从存储栈帧load到操作栈帧上 1: getfield #4 // 将 list 成员变量load到操作栈帧上 4: invokeinterface #7, 1 // 调用 list.iterator() 方法返回 iterator 临时迭代变量到操作栈帧上 9: astore_1 // 将栈帧栈帧上的新返回的迭代变量的引用存储到存储栈帧的1号slot中 10: aload_1 // 将 1 号slot中的迭代变量的引用load到操作栈帧上 11: invokeinterface #8, 1 // 调用Iterator接口声明的 hasNext() 方法,返回一个boolean(实际是 0或者1)到操作栈上 16: ifeq 39 // 如果返回的是0,则调到程序结束 19: aload_1 // 否则,将1号slot中的临时迭代变量load到操作栈帧上 20: invokeinterface #9, 1 // 调用临时迭代变量的 next() 方法,返回当前的 string 实例 25: checkcast #5 // 检查上一步返回时的引用类型是否是String类型 28: astore_2 // 将返回的 string 对象从操作栈保存到存储栈 29: getstatic #10 // 将 out 对象load到操作栈帧上 32: aload_2 // 将2号slot中的那个 string 对象load到操作栈帧上 33: invokevirtual #11 // 调用 out 对象的 println 方法 36: goto 10 // 循环继续 39: return
void testArray();
在下面的分析中你将看到,数组的foreach语法糖在编译时,是被分解成类似for的循环(准确的是while循环),与一般的循环语句相比并没有什么新的虚拟机指令使用。
0: aload_0 // 将 this 从存储栈帧load到操作栈帧上 1: getfield #6 // 将 array 成员变量load到操作栈帧上 4: astore_1 // 将操作栈帧中的 array存储到存储栈帧中 1 号slot里 5: aload_1 // 将存储栈中的 array load到操作栈帧中 6: arraylength // 计算 array 引用对应数组的长度 7: istore_2 // 将 array 的长度保存到存储栈帧的 2 号slot里 8: iconst_0 // 将常数 0 压入操作栈帧中 9: istore_3 // 将操作栈帧里的常数 0 保存到存储栈帧3 号 slot里 10: iload_3 // 将存储栈帧里 3 号slot中的常数 0 load到操作栈帧上 (数组下标计数变量) 11: iload_2 // 将存储栈帧 2 号slot中的 array 的长度值load到操作栈帧里 12: if_icmpge 34 // 比较操作栈帧里的数组的访问下标是否大于等于数组的长度,相等则跳转程序结束 15: aload_1 // 将存储栈帧中的 1 号slot中存储的 array 引用load到操作栈里 16: iload_3 // 将存储栈帧中 3 号slot中存储的数组访问下标变量load到操作栈帧中 17: aaload // 将 array 的当前操作栈帧中对应的下标位置的String引用load到操作栈帧中 18: astore 4 // 将操作栈帧上的String引用存储到存储栈帧的 4 号slot中 20: getstatic #10 // 将 out 对象load到操作栈帧上 23: aload 4 // 将存储栈帧中 4 号slot中的String变量load到操作栈中 25: invokevirtual #11 // 调用 out 对象的 println 方法 28: iinc 3, 1 // 将存储栈帧中 3 号 slot对应的值加1 并保存到原位置(数组下标加 1) 31: goto 10 // 继续循环 34: return
四、总结
1,既然没有”新的实质性质的东西“,那么为什么还要使用呢?
没有新的东西,也没有对效率向差的方向影响,但是可以发现这里的这里的循环变量(编译器编译时生成的迭代变量和数组访问下边变量)都被隐藏在循环语句的内部,这样就缩小的局部变量的作用域,如 Effective Java那本书上所提,这样可以防止变量作用域污染,如果误用出作用域的变量则在编译时就可以查出。更重要的是这样做某些情况下可以缩小函数调用栈帧中的存储栈帧的长度。当然如果现代虚拟机都有 活性分析优化,可以优化掉这部分的问题。
2 ,注释那部分mark的那条 checkCast字节码是干什么的?
其实这个和foreach语法糖没有任何关系,只是这里是泛型的问题,因为java泛型是使用checkCast
java foreach实现原理
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。