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 list = new ArrayList();
		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."":()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."":()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 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)更好的代码。


你可能感兴趣的:(Java)