Iterator 與 Iterable

September 29, 2022

如果要寫個 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 也空了,這並不是你想要的結果,怎麼辦呢?

Iterator/Iterable

無論是 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 以後,原先定義在 Collectioniterator 方法,提昇至新的 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

增強式 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 迴圈

增強式 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);

分享到 LinkedIn 分享到 Facebook 分享到 Twitter