Java 中那些繞不開的內(nèi)置接口 -- Iterator 和 Iterable

在平時開發(fā)中,讓我們直接手動進行 Java 對象的序列化場景非常少,更多是 框架、JVM 在幫我們做這個事兒,我們也介紹了 Java 序列化與 JSON 這樣通用序列化格式的區(qū)別,以及各自的優(yōu)勢,今天我們繼續(xù)介紹兩個從名字和功能上乍一看都很相似的基礎(chǔ)接口 -- Iterator 和 Iterable。

Java 的 Iterable 接口用來表示可迭代對象的集合,這意味著,實現(xiàn) 了 Iterable 接口的類可以迭代其元素。Java 里還有一個 Iterator 接口表示迭代器,迭代其實我們也可以簡單地理解為遍歷,是一個標準化遍歷各類 Collection 容器里面的所有對象的接口。Java 的 Iterator 接口相當于是從語言本身支持迭代器這種設(shè)計模式,在文中介紹 Iterator 接口前,我們會先介紹一下迭代器設(shè)計模式的思想,以及使用這種設(shè)計模式能獲得的收益。

Iterator 和 Iterable 這兩個接口在命名上相似,新手學(xué)習(xí)時容易混淆,所以在這里把兩者展開,梳理明白。本文大綱如下:



為避免混淆,Collection 和 集合指的是同類,而另外一個可以翻譯成集合的Set 數(shù)據(jù)結(jié)構(gòu)文中會直接用Set表示。


1

Iterator 接口

Iterator 接口的全限定名是 java.util.Iterator ,我們前面學(xué)習(xí)的 Java 集合框架里的各種容器也是 Iterator 接口的實現(xiàn)。java.util.Iterator和它的實現(xiàn)類是 Java 語言內(nèi)置提供的對迭代器模式的支持,這里我們先花點篇幅了解一下,到底什么是迭代器模式。



迭代器模式




迭代器模式用于遍歷集合中的對象,很多語言里都內(nèi)置了這種設(shè)計模式,迭代器模式的思想是將集合對象的遍歷操作從集合類中拆分出來,放到迭代器實現(xiàn)類中,讓兩者的職責(zé)更單一。

迭代器模式屬于 GoF 總結(jié)的 23 種設(shè)計模式中的行為型模式。在這種模式中,集合結(jié)構(gòu)會提供一個迭代器。通過這個迭代器可以順序遍歷集合中的每個元素而不需要暴露其具體的實現(xiàn)。其結(jié)構(gòu),可以用下面這個 UML 類圖表示:


Iterator 接口:這個接口會定義一些基礎(chǔ)的操作函數(shù),如hasNext()或getNext()等。通過名稱就可以看出,這些方法可以幫助我們執(zhí)行遍歷集合、重啟迭代等操作。

Collection 接口:這個接口代表了要被遍歷的集合。在這個接口里定義了一個createIterator方法,該方法會返回一個Iterator的實例。

Concrete Iterator:Iterator接口的具體實現(xiàn)類。

Concrete Collection:Collection接口的具體實現(xiàn)類。

上面這個迭代器模式的結(jié)構(gòu)中的各個角色映射到 Java 內(nèi)置為我們提供的迭代器模式實現(xiàn)中來的話,iterator接口就是一會兒要說到的java.util.iterator接口,它定義了模式中迭代器的行為規(guī)范;collection接口就是我們前面學(xué)過了集合框架的頂層接口java.util.collection; Concrete Iterator 和Concrete Collection即兩個頂層接口的實現(xiàn)類則對應(yīng)于前面幾節(jié)學(xué)過的集合框架中的List, Set這些實現(xiàn)類,當然初次之外,我們在 Java 里也可以根據(jù)自己的需求,自己實現(xiàn)java.util.iterator接口,來提供迭代器的自定義實現(xiàn)類。

了解完迭代器模式后,接下來我們看看java.util.iterator接口。

Java提供的迭代器接口
要使用 Iterator 遍歷元素,必須從要迭代的 Collection 對象中獲取一個 Iterator 實例。獲得的 Iterator 會跟蹤底層 Collection 中的元素,以確保能正確遍歷所有元素。如果在迭代器 Iterator 在迭代的過程中,代碼對其指向的底層 Collection 進行了修改,Iterator 通常會檢測到,并在下次嘗試從 Iterator 獲取下一個元素時拋出異常。

Iterator 的定義如下

public interface Iterator<E> {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}
Iterator 接口相當簡單,只定義了四個方法,其中兩個還提供了默認的實現(xiàn)。

方法名    描述
hasNext()    如果迭代器中還有元素,則返回 true,否則返回 false。
next()    從迭代器返回下一個元素
remove()    從迭代器正在迭代的集合中移除從 next() 返回的最新元素。
forEachRemaining()    迭代迭代器中的所有剩余元素,并調(diào)用 Lambda 表達式,將每個剩余元素作為參數(shù)傳遞給 lambda 表達式。
下面的內(nèi)容會介紹每個方法如何使用。

獲取迭代器
Java 集合接口 java.util.Collection 中包含一個稱為 iterator() 的方法。通過調(diào)用 iterator(),可以從給定的 Collection 中獲取迭代器。 Java 集合框架中實現(xiàn)了 Collection 接口的數(shù)據(jù)結(jié)構(gòu),比如列表、集合(Set)、隊列、雙端隊列等等,它們都實現(xiàn)了iterator() 方法。

下面是從各種集合類型獲取迭代器的幾個示例:

List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");

Iterator<String> iterator = list.iterator();


Set<String> set = new HashSet<>();
set.add("one");
set.add("two");
set.add("three");

Iterator<String> iterator2 = set.iterator();



迭代迭代器




可以使用 while 循環(huán)迭代 Iterator 中的對象。下面是一個使用 while 循環(huán)迭代  Iterator 元素的示例:

Iterator iterator = list.iterator();

while(iterator.hasNext()) {
    Object nextObject = iterator.next();

}

上面的例子中有兩個方法需要注意。第一個方法是 hasNext() 方法,如果 Iterator 包含更多元素,則返回 true。換句話說,如果 Iterator 還沒有遍歷完其指向的 Collection 中的所有元素 - hasNext() 方法將返回 true。如果 Iterator 已經(jīng)迭代了底層集合中的所有元素 - hasNext() 方法返回 false。要注意的第二個方法是 next() 方法。next() 方法返回迭代器正在迭代的 Collection 的下一個元素



迭代順序




Iterator 中包含的元素的遍歷順序取決于提供 Iterator 的對象。例如,從 List 獲得的迭代器將按照元素在 List 內(nèi)部存儲的相同順序遍歷該 List 的元素。而從 Set 獲得的 Iterator 不保證任何 Set 中元素迭代的順序。

迭代期間修改底層集合
某些集合不允許您在通過 Iterator 迭代時修改集合。在這種情況下,將在下次調(diào)用 Iterator next() 方法時拋出異ConcurrentModificationException。以下示例在執(zhí)行時程序會導(dǎo)致 ConcurrentModificationException:

List<String> list = new ArrayList<>();

list.add("123");
list.add("456");
list.add("789");

Iterator<String> iterator = list.iterator();

while(iterator.hasNext()) {
    String value = iterator.next();

    if(value.equals("456")){
        list.add("999");
    }
}

如果在通過 Iterator 迭代集合時修改了集合,則會拋出

ConcurrentModificationException 因為 Iterator 與底層集合不再同步。



迭代過程中刪除元素




上面說了在迭代過程中修改迭代器指向的底層集合會導(dǎo)致拋出異常,但是 Iterator 接口也有一個 remove() 方法,它允許我們從底層集合中刪除 next() 剛剛返回的元素。調(diào)用 remove() 不會導(dǎo)致拋出

ConcurrentModificationException。

List<String> list = new ArrayList<>();

list.add("123");
list.add("456");
list.add("789");

Iterator<String> iterator = list.iterator();

while(iterator.hasNext()) {
    String value = iterator.next();

    if(value.equals("456")){
        iterator.remove();
    }
}


forEachRemaining




Iterator 的 forEachRemaining() 方法可以在內(nèi)部迭代 Iterator 中剩余的所有元素,并為每個元素調(diào)用作為參數(shù)傳遞給 forEachRemaining() 的 Lambda 表達式。以下是使用 forEachRemaining() 方法的示例:

List<String> list = new ArrayList<>();
list.add("Jane");
list.add("Heidi");
list.add("Hannah");

Iterator<String> iterator = list.iterator();
        
iterator.forEachRemaining((element) -> {
    System.out.println(element);
});



雙向迭代器




Java 還包含一個名為 ListIterator 的接口,它擴展了 Iterator 接口,表示雙向一個迭代器 - 意味著我們可以在迭代中向前和向后導(dǎo)航元素的迭代器。下面的例程展示了如何使用雙向迭代器向前后和向后迭代。

List<String> list = new ArrayList<>();
list.add("Jane");
list.add("Heidi");
list.add("Hannah");

ListIterator<String> listIterator = list.listIterator();
        
while(listIterator.hasNext()) {
    System.out.println(listIterator.next());
}
        
while(listIterator.hasPrevious()) {
    System.out.println(listIterator.previous());
}

示例首先通過所有元素向前迭代 ListIterator,然后再次通過所有元素向后迭代回到第一個元素。








Iterator 的自定義實現(xiàn)




如果你有一個特殊的、自定義集合類型,你可以通過自己實現(xiàn) Iterator 接口來創(chuàng)建一個迭代器,它可以遍歷你的自定義集合的元素。

在本節(jié)中,我將展示 Iterator 接口的一個超級簡單的自定義實現(xiàn)。例子實現(xiàn)的比較粗糙,沒有考慮在迭代過程中檢測內(nèi)部元素是否被更改之類的問題,但是足以讓你了解 Iterator 的實現(xiàn)應(yīng)該長什么樣。

import java.util.Iterator;
import java.util.List;

public class ListIterator <T> implements Iterator<T> {

    private List<T> source = null;
    private int index = 0;

    public ListIterator(List<T> source){
        this.source = source;
    }


    @Override
    public boolean hasNext() {
        return this.index < this.source.size();
    }

    @Override
    public T next() {
        return this.source.get(this.index++);
    }

remove() 和 foreachRemaining() 方法在 Iterator 接口里提供了默認實現(xiàn),所以我們這個例子只提供了 hasNext() 和 next() 兩個方法的實現(xiàn)。這個實現(xiàn)類的使用方法如下:

import java.util.ArrayList;
import java.util.List;

public class ListIteratorExample {

    public static void main(String[] args) {
        List<String> list = new ArrayList();

        list.add("one");
        list.add("two");
        list.add("three");

        ListIterator<String> iterator = new ListIterator<>(list);
        while(iterator.hasNext()) {
            System.out.println( iterator.next() );
        }

    }
}


2

Iterable 接口

Iterable 接口的全稱是 java.lang.Iterable ,它表示可迭代對象的集合,這意味著,實現(xiàn) Iterable 接口的類可以迭代其元素。

Iterable 接口定義了三個方法,其中兩個提供了默認實現(xiàn),只有 iterator() 方法是要求實現(xiàn)類必須實現(xiàn)的,該方法返回一個上個章節(jié)已經(jīng)介紹過的 Iterator 迭代器。

public interface Iterable<T> {

    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

java.util.Collection 接口擴展了 Iterable 接口,所以集合框架里的 Collection 類族也都是 Iterable 的實現(xiàn)類--即他們的對象都可以迭代內(nèi)部元素。

在 Java 里有三種方式可以迭代 Iterable 實現(xiàn)類的對象:通過 for-each 循環(huán)、獲取 Iterable 實現(xiàn)類對象的迭代器(Iterator) 或者是通過調(diào)用 Iterable 的 forEach() 方法。



使用for-each循環(huán)迭代Iterable對象




下面的示例展示了如何通過 Java 的 for-each 循環(huán)迭代 列表的元素。由于Java 的 List 接口擴展了Collection 接口,而Collection 接口又擴展了Iterable 接口,因此List 對象被 for-each 循環(huán)迭代。

List<String> list = new ArrayList><();

list.add("one");
list.add("two");
list.add("three");

for( String element : list ){
    System.out.println( element.toString() );
}



通過迭代器迭代Iterable對象




迭代  Iterable 對象元素的第二種方法是,通過調(diào)用Collection 提供的iterator() 方法從中獲取指向?qū)ο笤氐牡?,然后使用迭代器迭代元素?br>
List<String> list = new ArrayList><();

list.add("one");
list.add("two");
list.add("three");

Iterator<String> iterator = list.iterator();

while(iterator.hasNext()) {
    String element = iterator.next();
    System.out.println( element );
}



使用forEach方法迭代 Iterable 對象




迭代 Iterable 對象元素的第三種方法是通過其 forEach() 方法。forEach() 方法使用 Lambda 表達式作為參數(shù)。對 Iterable 對象中的每個元素,都會調(diào)用一次此 lambda 表達式。

List<String> list = new ArrayList><();

list.add("one");
list.add("two");
list.add("three");

list.forEach( (element) -> {
    System.out.println( element );
});



編寫自己的 Iterable 實現(xiàn)




需要自己 Iterable 的需求很少,一般 集合框架里提供的數(shù)據(jù)結(jié)構(gòu)就能滿足我們的開發(fā)需求了。不過為了演示,下面給出 Iterable 接口的一個超簡單的自定義實現(xiàn)。

public class Persons implements Iterable {
    private List<Person> persons;
    
    public Persons(List<Person> persons) {
        this.persons = persons;
    }
    
    public Iterator<Person> iterator() {
        return this.persons.iterator();
    }
}


3

總結(jié)

本篇文章給大家梳理了 Java 內(nèi)置提供的 Iterator 和 Iterable接口,兩者都跟集合對象的迭代有關(guān),相信大家在看 Java 提供的一些內(nèi)置對象、工具類方法的參數(shù)定義里見過這兩個從名字上看長有點類似的接口類型。相信看過這篇文章,會減少你不少--“這兩塊貨都是啥?”的疑惑。

在介紹Iterator 接口時我們提到了,它是 Java 語言為我們提供的迭代器這種設(shè)計模式的在語言層面的支持,也對迭代器設(shè)計模式做了一些解釋,希望你能喜歡本篇內(nèi)容





作者:碼出宇宙


歡迎關(guān)注微信公眾號 :碼出宇宙

掃描添加好友邀你進技術(shù)交流群,加我時注明【姓名+公司(學(xué)校)+職位】