概述 循环作为程序中经常使用的语句,在java5之后推出了新的for/in(foreach)循环方式以方便程序员编写(阅读)代码。这种方式并不是新的语法,只是语法糖。即编写的foreach循环的代码并不是直接转成字节码,而是由编译器先转成对应的语法,然后再转成字节码,可以理解成是编译器对一些语法的封装提供的另一种方便阅读编写功能代码的实现方式。java中提供的foreach语法糖其底层实现方式主要有两种:对于集合类或实现迭代器的集合使用迭代器的遍历方式,对于数组集合使用数组的遍历方法。
 
迭代器遍历模式 对于实现Iterator接口的集合,使用foreach实现循环功能的代码会被编译器转换成使用迭代器遍历集合的代码,然后再转成字节码。例如以下的程序,使用foreach循环遍历ArrayList集合,使用javac TestForEach.java生成字节码后,再使用javap -verbose TestForEach进行反编译,从反编译的结果来看,可以看出其底层是用迭代器模式进行遍历的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import  java.util.ArrayList;import  java.util.List;public  class  TestForEach {     public  static  void  main (String args[])      {         List<Integer> nums = new  ArrayList <>();         nums.add(11 );         nums.add(22 );         nums.add(33 );         for  (Integer num : nums)         {             System.out.println(num);         }     } } 
 
反编译结果如下,从中可以看出,在106118这十几行中是对集合进行遍历输出,在106行先使用List.iterator()接口生成迭代器,然后在109118中不断使用Iterator.hasNext()判断是否有下个元素,有则使用Iterator.next()接口获取下个元素并进行输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 Classfile /C:/Users/zhchun/Desktop/TestForEach.class    Last  modified 2018 -7 -22 ; size 842  bytes   MD5 checksum 45751115d8755b894835c52451125338   Compiled from "TestForEach.java"  public  class  TestForEach   SourceFile: "TestForEach.java"    minor version: 0    major version: 52    flags: ACC_PUBLIC, ACC_SUPER Constant pool:    #1  = Methodref          #13. #25             #2  = Class              #26                 #3  = Methodref          #2. #25              #4  = Methodref          #9. #27              #5  = InterfaceMethodref #28. #29             #6  = InterfaceMethodref #28. #30             #7  = InterfaceMethodref #31. #32             #8  = InterfaceMethodref #31. #33             #9  = Class              #34                #10  = Fieldref           #35. #36            #11  = Methodref          #37. #38            #12  = Class              #39                #13  = Class              #40                #14  = Utf8               <init>   #15  = Utf8               ()V   #16  = Utf8               Code   #17  = Utf8               LineNumberTable   #18  = Utf8               main   #19  = Utf8               ([Ljava/lang/String;)V   #20  = Utf8               StackMapTable   #21  = Class              #41                #22  = Class              #42                #23  = Utf8               SourceFile   #24  = Utf8               TestForEach.java   #25  = NameAndType        #14 :#15            #26  = Utf8               java/util/ArrayList   #27  = NameAndType        #43 :#44            #28  = Class              #41                #29  = NameAndType        #45 :#46            #30  = NameAndType        #47 :#48            #31  = Class              #42                #32  = NameAndType        #49 :#50            #33  = NameAndType        #51 :#52            #34  = Utf8               java/lang/Integer   #35  = Class              #53                #36  = NameAndType        #54 :#55            #37  = Class              #56                #38  = NameAndType        #57 :#58            #39  = Utf8               TestForEach   #40  = Utf8               java/lang/Object   #41  = Utf8               java/util/List   #42  = Utf8               java/util/Iterator   #43  = Utf8               valueOf   #44  = Utf8               (I)Ljava/lang/Integer;   #45  = Utf8               add   #46  = Utf8               (Ljava/lang/Object;)Z   #47  = Utf8               iterator   #48  = Utf8               ()Ljava/util/Iterator;   #49  = Utf8               hasNext   #50  = Utf8               ()Z   #51  = Utf8               next   #52  = Utf8               ()Ljava/lang/Object;   #53  = Utf8               java/lang/System   #54  = Utf8               out   #55  = Utf8               Ljava/io/PrintStream;   #56  = Utf8               java/io/PrintStream   #57  = Utf8               println   #58  = Utf8               (Ljava/lang/Object;)V {   public  TestForEach () ;     descriptor: ()V     flags: ACC_PUBLIC     Code:       stack=1 , locals=1 , args_size=1           0 : aload_0                 1 : invokespecial #1                             4 : return                LineNumberTable:         line 4 : 0    public  static  void  main (java.lang.String[]) ;     descriptor: ([Ljava/lang/String;)V     flags: ACC_PUBLIC, ACC_STATIC     Code:       stack=2 , locals=4 , args_size=1           0 : new            #2                             3 : dup                     4 : invokespecial #3                             7 : astore_1                8 : aload_1                 9 : bipush        11          11 : invokestatic  #4                            14 : invokeinterface #5 ,  2                      19 : pop                    20 : aload_1                21 : bipush        22          23 : invokestatic  #4                            26 : invokeinterface #5 ,  2                      31 : pop                    32 : aload_1                33 : bipush        33          35 : invokestatic  #4                            38 : invokeinterface #5 ,  2                      43 : pop                    44 : aload_1                45 : invokeinterface #6 ,  1                      50 : astore_2               51 : aload_2                52 : invokeinterface #7 ,  1                      57 : ifeq          80          60 : aload_2                61 : invokeinterface #8 ,  1                      66 : checkcast     #9                            69 : astore_3               70 : getstatic     #10                           73 : aload_3                74 : invokevirtual #11                           77 : goto          51          80 : return                LineNumberTable:         line 8 : 0          line 9 : 8          line 10 : 20          line 11 : 32          line 13 : 44          line 15 : 70          line 16 : 77          line 17 : 80        StackMapTable: number_of_entries = 2             frame_type = 253             offset_delta = 51            locals = [ class  java /util/List, class  java /util/Iterator ]              frame_type = 250               offset_delta = 28    } 
 
因此,上面使用foreach方式遍历集合的程序与下面使用迭代器模式进行遍历的程序是一样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import  java.util.ArrayList;import  java.util.List;import  java.util.Iterator;public  class  TestForEach {     public  static  void  main (String args[])      {         List<Integer> nums = new  ArrayList <>();         nums.add(11 );         nums.add(22 );         nums.add(33 );                           Iterator  iter  =  nums.iterator();         while  (iter.hasNext())          {             Integer  num  =  (Integer)iter.next();             System.out.println(num);             }     } } 
 
数组依次遍历模式 数组没有实现Iterator接口,但是又要支持foreach语法糖,所以就用了最原始的最基本的依次遍历数组中的每个元素的方式来实现。如下代码是数组用foreach方式实现的遍历。
1 2 3 4 5 6 7 8 9 10 11 public  class  TestForEach {     public  static  void  main (String args[])      {         int [] nums = {11 , 22 , 33 };         for  (int  num : nums)         {             System.out.println(num);         }     } } 
 
同样使用javac TestForEach.java生成字节码后,再使用javap -verbose TestForEach进行反编译,输出结果如下。从中可以看出,从80~92这十几行是对数组进行遍历输出,这个过程没有使用迭代器,只是不断的对数进行出栈、比较、入栈、输出结果的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 Classfile /C:/Users/zhchun/Desktop/TestForEach.class    Last  modified 2018 -7 -22 ; size 528  bytes   MD5 checksum 874d6164dd77ec1874a96f4adb7d884b   Compiled from "TestForEach.java"  public  class  TestForEach   SourceFile: "TestForEach.java"    minor version: 0    major version: 52    flags: ACC_PUBLIC, ACC_SUPER Constant pool:    #1  = Methodref          #5. #17              #2  = Fieldref           #18. #19             #3  = Methodref          #20. #21             #4  = Class              #22                 #5  = Class              #23                 #6  = Utf8               <init>    #7  = Utf8               ()V    #8  = Utf8               Code    #9  = Utf8               LineNumberTable   #10  = Utf8               main   #11  = Utf8               ([Ljava/lang/String;)V   #12  = Utf8               StackMapTable   #13  = Class              #24                #14  = Class              #25                #15  = Utf8               SourceFile   #16  = Utf8               TestForEach.java   #17  = NameAndType        #6 :#7              #18  = Class              #26                #19  = NameAndType        #27 :#28            #20  = Class              #29                #21  = NameAndType        #30 :#31            #22  = Utf8               TestForEach   #23  = Utf8               java/lang/Object   #24  = Utf8               [Ljava/lang/String;   #25  = Utf8               [I   #26  = Utf8               java/lang/System   #27  = Utf8               out   #28  = Utf8               Ljava/io/PrintStream;   #29  = Utf8               java/io/PrintStream   #30  = Utf8               println   #31  = Utf8               (I)V {   public  TestForEach () ;     descriptor: ()V     flags: ACC_PUBLIC     Code:       stack=1 , locals=1 , args_size=1           0 : aload_0                 1 : invokespecial #1                             4 : return                LineNumberTable:         line 1 : 0    public  static  void  main (java.lang.String[]) ;     descriptor: ([Ljava/lang/String;)V     flags: ACC_PUBLIC, ACC_STATIC     Code:       stack=4 , locals=6 , args_size=1           0 : iconst_3                1 : newarray       int           3 : dup                     4 : iconst_0                5 : bipush        11           7 : iastore                 8 : dup                     9 : iconst_1               10 : bipush        22          12 : iastore                13 : dup                    14 : iconst_2               15 : bipush        33          17 : iastore                18 : astore_1               19 : aload_1                20 : astore_2               21 : aload_2                22 : arraylength            23 : istore_3               24 : iconst_0               25 : istore        4          27 : iload         4          29 : iload_3                30 : if_icmpge     53          33 : aload_2                34 : iload         4          36 : iaload                 37 : istore        5          39 : getstatic     #2                            42 : iload         5          44 : invokevirtual #3                            47 : iinc          4 , 1          50 : goto          27          53 : return                LineNumberTable:         line 5 : 0          line 6 : 19          line 8 : 39          line 6 : 47          line 10 : 53        StackMapTable: number_of_entries = 2             frame_type = 255             offset_delta = 27            locals = [ class "[Ljava/lang/String;" , class "[I" , class "[I" , int , int  ]           stack = []            frame_type = 248             offset_delta = 25  } 
 
对于用foreach方式实现的数组遍历方式,与下面的依次遍历数组中每个元素的方式是一样的
1 2 3 4 5 6 7 8 9 10 11 12 public  class  TestForEach {     public  static  void  main (String args[])      {         int [] nums = {11 , 22 , 33 };         for  (int  i  =  0 ; i < nums.length; i++)         {             int  num  =  nums[i];             System.out.println(num);         }     } } 
 
其他 虽然foreach方便了程序的编写和阅读,是遍历集合和数组的一种好方式,但是使用foreach进行集合遍历时需要额外注意不能对集合长度进行修改,也就是不能对集合进行增删操作,否则会抛出ConcurrentModificationException异常。例如,下面程序会在执行第13行时抛出ConcurrentModificationException异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import  java.util.ArrayList;import  java.util.List;public  class  TestForEach {     public  static  void  main (String args[])      {         List<Integer> nums = new  ArrayList <>();         nums.add(11 );         nums.add(22 );         nums.add(33 );         for  (Integer num : nums)         {             if  (num == 11 )             {                                  nums.remove((Integer)num);             }             else              {                 System.out.println(num);             }         }     } } 
 
虽然ArrayList的foreach底层用迭代器实现,迭代器也支持在遍历集合的过程中进行删除元素的操作,但是删除的函数必须是迭代器的函数,而不是集合自有的函数。至于上述代码为什么会抛出ConcurrentModificationException异常,可以从ArrayList中的迭代器类找到答案。ArrayList中部分源码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 public  boolean  remove (Object o)  {    if  (o == null ) {         for  (int  index  =  0 ; index < size; index++)             if  (elementData[index] == null ) {                 fastRemove(index);                 return  true ;             }     } else  {         for  (int  index  =  0 ; index < size; index++)             if  (o.equals(elementData[index])) {                 fastRemove(index);                 return  true ;             }     }     return  false ; } private  void  fastRemove (int  index)  {    modCount++;     int  numMoved  =  size - index - 1 ;     if  (numMoved > 0 )         System.arraycopy(elementData, index+1 , elementData, index,                          numMoved);     elementData[--size] = null ;  } public  Iterator<E> iterator ()  {    return  new  Itr (); } private  class  Itr  implements  Iterator <E> {    int  cursor;            int  lastRet  =  -1 ;      int  expectedModCount  =  modCount;     public  boolean  hasNext ()  {         return  cursor != size;     }     @SuppressWarnings("unchecked")      public  E next ()  {         checkForComodification();         int  i  =  cursor;         if  (i >= size)             throw  new  NoSuchElementException ();         Object[] elementData = ArrayList.this .elementData;         if  (i >= elementData.length)             throw  new  ConcurrentModificationException ();         cursor = i + 1 ;         return  (E) elementData[lastRet = i];     }     public  void  remove ()  {         if  (lastRet < 0 )             throw  new  IllegalStateException ();         checkForComodification();         try  {             ArrayList.this .remove(lastRet);                          cursor = lastRet;             lastRet = -1 ;                          expectedModCount = modCount;         } catch  (IndexOutOfBoundsException ex) {             throw  new  ConcurrentModificationException ();         }     }     @Override      @SuppressWarnings("unchecked")      public  void  forEachRemaining (Consumer<? super  E> consumer)  {         Objects.requireNonNull(consumer);         final  int  size  =  ArrayList.this .size;         int  i  =  cursor;         if  (i >= size) {             return ;         }         final  Object[] elementData = ArrayList.this .elementData;         if  (i >= elementData.length) {             throw  new  ConcurrentModificationException ();         }         while  (i != size && modCount == expectedModCount) {             consumer.accept((E) elementData[i++]);         }                  cursor = i;         lastRet = i - 1 ;         checkForComodification();     }     final  void  checkForComodification ()  {         if  (modCount != expectedModCount)             throw  new  ConcurrentModificationException ();     } } 
 
当执行for (Integer num : nums)语句时,会先调用ArrayList中的iterator()接口生成迭代器,而在初始化Itr类时会先将ArrayList对象中的modCount变量赋给Itr对象中的expectedModCount变量,在调用迭代器的next函数时会先调用checkForComodification函数进行校验,如果expectedModCount和modCount不相等则会抛出ConcurrentModificationException异常。在正常的集合遍历中,一般情况下,我们只使用迭代器中hasNext和next函数,并不会改变expectedModCount或者modCount的值,所以不会有问题,但是如果在遍历中调用了集合中自有的删除函数操作,则会改变modCount的值,从而导致expectedModCount与modCount不相等,进而在调用迭代器的next函数时进行校验不通过产生ConcurrentModificationException异常。而在遍历中调用迭代器的删除函数操作,由于其内部会在删除元素后对expectedModCount重新赋值,使其与modCount值相等,所以在遍历集合的过程中使用迭代器的删除函数操作不会有问题。
正确的在遍历集合过程中进行删除操作的方式如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import  java.util.ArrayList;import  java.util.List;import  java.util.Iterator;public  class  TestForEach {     public  static  void  main (String args[])      {         List<Integer> nums = new  ArrayList <>();         nums.add(11 );         nums.add(22 );         nums.add(33 );         Iterator<Integer> iter = nums.iterator();         while  (iter.hasNext())          {             Integer  num  =  iter.next();             if  (num == 11 )             {                                                   iter.remove();             }             else              {                 System.out.println(num);                }          }              } } 
 
参考资料 [1] 朱小厮. Java语法糖之foreach[J/OL]. https://blog.csdn.net/u013256816/article/details/50736498  , 2016-02-25