Iterable 與 Iterator


如果要你寫個forEach()方法,可以顯示List收集的所有物件,也許你會這麼寫:

...
    public static void forEach(List list) {
        int size = list.size();
        for(int i = 0; i < size; i++) {
            out.println(list.get(i));
        }
    }
...

這個方法適用於所有實作List介面的物件,例如ArrayListLinkedList等。如果要讓你寫個forEach()方法顯示Set收集的所有物件,你該怎麼寫呢?在查看過Set的API文件後,你發現有個toArray()方法,可以將Set收集的物件轉為Object[]傳回,所以你會這麼撰寫:

...
    public static void forEach(Set set) {
        for(Object o : set.toArray()) {
            out.println(o);
        }
    }
...

這個方法適用於所有實作Set介面的物件,例如HashSetTreeSet等。如果現在要讓你再實作一個forEach()方法,可以顯示Queue收集的所有物件,也許你會這麼寫:

...
    public static void forEach(Queue queue) {
        while(queue.peek() != null) {
            out.println(queue.poll());
        }
    }
...

表面上看來好像正確,不過Queuepoll()方法會取出物件,當你顯示完Queue中所有物件,Queue也空了,這並不是你想要的結果,怎麼辦呢?

事實上,無論是ListSetQueue,都會有個iterator()方法,這個方法在JDK1.4之前,是定義在Collection介面中,而ListSetQueue繼承自Collection,所以也都擁有iterator()的行為。

iterator()方法會傳回java.util.Iterator介面的實作物件,這個物件包括了Collection收集的所有物件,你可以使用IteratorhasNext()看看有無下一個物件,若有的話,再使用next()取得下一個物件。因此,無論是ListSetQueue或任何Collection,都可以使用以下的forEach()來顯示所收集之物件:

...
    public static void forEach(Collection collection) {
        Iterator iterator = collection.iterator();
        while(iterator.hasNext()) {
            out.println(iterator.next());
        }
    }
...

在JDK5之後,原先定義在Collection中的iterator()方法,提昇至新的java.util.Iterable父介面,因此在JDK5之後,你可以使用以下的forEach()方法顯示收集的所有物件:

...
    public static void forEach(Iterable iterable) {
        Iterator iterator = iterable.iterator();
        while(iterator.hasNext()) {
            out.println(iterator.next());
        }
    }
...


任何實作Iterable的物件,都可以使用這個forEach()方法,而不一定要是Collection,像是 神奇的 foreach 中實作的IterableString

在JDK5之後有了增強式for迴圈,先前看到它運用在陣列上,實際上,增強式for迴圈還可運用在實作Iterable介面的物件上,因此先前的forEach()方法,可以用增強式for迴圈更加簡化:

package cc.openhome;

import java.util.*;

public class ForEach {
    public static void main(String[] args) {
        List names = Arrays.asList("Justin", "Monica", "Irene");
        forEach(names);
        forEach(new HashSet(names)); 
        forEach(new ArrayDeque(names)); 
    }

    public static void forEach(Iterable iterable) {
        for(Object o : iterable) {
            System.out.println(o);
        }
    }
}

這邊使用了java.util.Arraysstatic方法asList(),這個方法接受不定長度引數,可將指定的引數收集為ListList是一種Iterable,所以可以使用forEach()方法。HashSet具有接受Collection的建構式,List是一種Collection,所以可用來建構HashSet,而Set是一種Iterable,所以可使用forEach()方法。同理,ArrayDeque具有接受Collection的建構式,List是一種Collection,所以可用來建構ArrayDequeDeque是一種Iterable,所以可使用forEach()方法。

實際上增強式for迴圈是編譯器蜜糖,當運用在Iterable物件時,會展開為:

public static void forEach(Iterable iterable) {
    Object o;
    for(Iterator i\$ = iterable.iterator(); i\$.hasNext(); System.out.println(o)) {
        o = i\$.next();
    }
}

可以看到,實際上還是呼叫了iterator()方法,運用傳回的Iterator物件來迭代取得收集之物件。

如果使用JDK8,想要迭代物件還有新的選擇,Iterable上新增了forEach()方法,可以讓你迭代物件進行指定處理:

List<String> names = Arrays.asList("Justin", "Monica", "Irene");
names.forEach(name -> out.println(name));
new HashSet(names).forEach(name -> out.println(name));
new ArrayDeque(names).forEach(name -> out.println(name));

發現了嗎?寫了三個重複的Lambda表示式,依照 方法與建構式參考 的說明,你可以直接參考outprintln()方法:

List<String> names = Arrays.asList("Justin", "Monica", "Irene");
names.forEach(out::println);
new HashSet(names).forEach(out::println);
new ArrayDeque(names).forEach(out::println);

嗯?Iterable介面新增了forEach()方法?如果你瞭解JDK8之前的介面,應該知道,介面一但新增了方法,所有實作介面的類別都得實作該方法,現存各種API中實作Iterable介面的類別太多了,這樣不會造成這些類別因為沒有實作forEach(),而在JDK8上編譯錯誤嗎?

不會的!因為JDK8演進了interface語法,允許擁有預設方法(Default method),這在下一篇就會討論。