首页 > 代码库 > For-Each循环

For-Each循环

For-Each是Java中For-Index的一种加强,是Java 5带来的新语法糖。

 

什么场合应该使用它?

For-Each似乎并不是必须的,但很多场合下使用它十分合适。

在实际开发中,经常会出现需要遍历数组,或是Collection容器的情况,就像source1那样。

1 /**
2  * source1
3  */
4 String[] args = {"a", "b", "c"};5 for (int i = 0; i < args.length; i++) {
6     String arg = args[i];
7     System.out.println(arg);
8 }

source1中的for循环仅仅希望得到args数组中的每一个元素,但是看看为此做了多少额外的事情,

第一件事。在循环了声明了下标i。真正归 For-Index 负责的任务其实是在满足要求的情况下不停地i++,所以在{}代码块中定义的代码块更像是一种附带——由于i在不断自增,那么在代码块中“顺便”可以取到args中的每一个值。

第二件事。For-Index 只关注int i。所以想要取得每一个元素,还得声明一个arg变量来引用每一个元素(虽然source1中,arg只被用到了一次,但实际开发中常常不只如此,根据DONT REPEAT YOURSELF原则,arg是必要的)。

 

而使用For-Each,在编码上解决了这两个问题。

1 /**
2  * source2
3  */
4 String[] args = {"a", "b", "c"};
5 for (String arg : args) {
6     System.out.println(arg);
7 }

For-Each直接()中声明了arg引用,不需要在代码块中专门声明。int i也不再必要了,For-Each会循环到args中无值可取为止。

显然,单纯为了遍历数组或容器对象中的每个元素,For-Each比For-Index在编码上更合适。在可读性方法,For-Each很容易让人知道设计者希望遍历冒号后面对象的全部元素。

Deolin在一开始并不习惯使用For-Each,直到忍受不了符合格式规约的For-Index需要在()中写大量的空格。

 

哪些类型的对象可以适用For-Each?

  • 数组
  • Collection类
  • 任何实现了Iterable接口的自定义类

(根据面向接口的思想,Deolin习惯把第三类对象称之为“可迭代的”对象)

第一类,第二类在实际开发中经常用到,而第三类能够适用For-Each的原因需要通过源码来进行分析。

 

For-Each的原理是什么?

 首先,尝试着把For-Each的对象改成一个String,

1 /**
2  * source3
3  */
4 String oneArg = "a";
5 for (String arg : oneArg) { }

发生了一个编译错误——Can only iterate over an array or an instance of java.lang.Iterable,

For-Each无法遍历数组和“可遍历的”实例以外的对象。

那么调查一下java.lang.Iterable的source,这个接口只有一个方法声明——Iterator<T> iterator()(Java 8之后又追加了两个default方法),

也就是说,适用For-Each的对象实际上都实现了Iterator<T> iterator()方法。

For-Each是通过调用Iterator obj的iterator()方法获得一个“迭代子”(Iterator对象)来实现迭代的。

即,身为了一个“可迭代的”对象,必须实现“返回一个迭代子”的方法,让外部能借此来迭代自己。

 

For-Each取得Iterator对象之后,会反复调用hasNext()和next()方法,原理与while类似

 1 /**
 2  * source4        
 3  */
 4 List<String> list = new ArrayList<String>();
 5 list.add("a");
 6 list.add("b");
 7 list.add("c");
 8 
 9 Iterator iterator = list.iterator();
10 while (iterator.hasNext()) {
11     String s = (String) iterator.next();
12     System.out.println(s);
13 }

在java.util.ArrayList$Itr的hasNext()和next()中添加两个断点可以印证这点。

 

利用For-Each的原理定制化迭代策略

 现在定义一个List(source5),如果想要实现倒序输出,通过代理模式定制一个反迭代器似乎是个不错的方法。

1 /**
2  * source5
3  */
4 List<String> list = new ArrayList<String>();
5 list.add("a");
6 list.add("b");
7 list.add("c");
8 list.add("d");

 

首先,定义一个代理类,使其能够持有一个Collection对象,并代理它的行为。

并且在定义一个向For-Each提供Iterable对象的对外方法。

 1 /**
 2  * source6
 3  */
 4 class ReversibleList<T> {
 5     // 持有一个List
 6     private List<T> list;
 7 
 8     public ReversibleList(List<T> list) {
 9         this.list = list;
10     }
11 
12     public int size() {
13         return list.size();
14     }
15 
16     public T get(int i) {
17         return list.get(i);
18     }
19 
20     public Iterable<T> getIterableObj() {
21         return new Iterable<T>() {
22 
23             @Override
24             public Iterator<T> iterator() {
25                 // 定制的迭代器
26                 return new Iterator<T>() {
27 
28                     private int index = size() - 1;
29 
30                     @Override
31                     public boolean hasNext() {
32                         return index > -1;
33                     }
34 
35                     @Override
36                     public T next() {
37                         return get(index --);
38                     }
39 
40                 };
41             }
42 
43         };
44     }

 

测试一下

 1     /**
 2      * source7
 3      */
 4     public static void main(String[] args) {
 5         List<String> list = new ArrayList<String>();
 6         list.add("a");
 7         list.add("b");
 8         list.add("c");
 9         list.add("d");
10         ReversibleList<String> rl = new ReversibleList<String>(list);
11         for(String s : rl.getIterableObj()) {
12             System.out.println(s);
13         }
14     }

 

输出结果

d
c
b
a

 

For-Each循环