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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM