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