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修改上传文件大小限制
- 下一篇:没有了
