Java foreach语法糖探秘
前不久线上用foreach循环,由于没判空,导致线上报了很多空指针异常。这两天梳理下foreach语法糖,看看jvm底层到底如何实现的。
先写一个foreach的demo,包含对list对象和数组对象的遍历。
package com.jd.lvsheng; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (String s : list) { System.out.println(s); } String[] arr = new String[3]; for (String s : arr) { System.out.println(s); } } }
然后对生成的class文件执行javap命令:javap -c Main,于是可以看这个文件的字节码了。
Compiled from "Main.java" public class com.jd.lvsheng.Main extends java.lang.Object{ public com.jd.lvsheng.Main(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2; //class java/util/ArrayList 3: dup 4: invokespecial #3; //Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: invokeinterface #4, 1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 14: astore_2 15: aload_2 16: invokeinterface #5, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z 21: ifeq 44 24: aload_2 25: invokeinterface #6, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 30: checkcast #7; //class java/lang/String 33: astore_3 34: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream; 37: aload_3 38: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 41: goto 15 44: iconst_3 45: anewarray #7; //class java/lang/String 48: astore_2 49: aload_2 50: astore_3 51: aload_3 52: arraylength 53: istore 4 55: iconst_0 56: istore 5 58: iload 5 60: iload 4 62: if_icmpge 85 65: aload_3 66: iload 5 68: aaload 69: astore 6 71: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream; 74: aload 6 76: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 79: iinc 5, 1 82: goto 58 85: return }
从第9,16,25行可以看出来,foreach在遍历List对象的时候,实则试用iterator迭代器来进行循环遍历的。循环的过程体现在21,41行。21行的ifeg命令会拿栈顶值和0比较,如果相等,则跳到44行,结束这个for循环。如果不等,会顺序执行到44行,goto 15, 也就是跳回15行。这个很像汇编语言for循环的实现。这段代码的实际等效java代码如下:
Iterator<String> iter = list.iterator(); while (iter.hasNext()) { System.out.println(iter.next()); }
再来看数组,数组的实现机制跟List完全不一样!因为数组并没有实现Iterator接口。
数组是先获取数组对象的长度,然后根据这个长度来遍历的。52行的指令是arraylength,用于获得数组的长度值并压入栈顶。而62和82行构成了一个循环。if_icmpge会比较栈顶两int型数值大小,当结果大于等于0时跳转到85行。79行iinc会将第5个变量自增1.这不就是一个for循环吗!这段字节码的实际等效java代码如下:
int len = arr.length; for (int i = 0; i < len; i++) { System.out.println(arr[i]); }
分析到这里,心里就清楚了,要用foreach循环,必须保证要遍历的对象非空。语法题虽然很甜,使用方便,但是不能觉解其底层实现,用起来心惊胆战的。。。
只有知其然且知其所以然,才能写出鲁棒性(Robustness)更好的代码。
声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。
- 上一篇: nginx修改上传文件大小限制
- 下一篇:没有了