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

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

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

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



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


1

Iterator 接口

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



迭代器模式




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

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


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

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

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

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

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

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

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

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 接口相當(dāng)簡(jiǎn)單,只定義了四個(gè)方法,其中兩個(gè)還提供了默認(rèn)的實(shí)現(xiàn)。

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

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

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

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 中的對(duì)象。下面是一個(gè)使用 while 循環(huán)迭代  Iterator 元素的示例:

Iterator iterator = list.iterator();

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

}

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



迭代順序




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

迭代期間修改底層集合
某些集合不允許您在通過(guò) Iterator 迭代時(shí)修改集合。在這種情況下,將在下次調(diào)用 Iterator next() 方法時(shí)拋出異ConcurrentModificationException。以下示例在執(zhí)行時(shí)程序會(huì)導(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");
    }
}

如果在通過(guò) Iterator 迭代集合時(shí)修改了集合,則會(huì)拋出

ConcurrentModificationException 因?yàn)?Iterator 與底層集合不再同步。



迭代過(guò)程中刪除元素




上面說(shuō)了在迭代過(guò)程中修改迭代器指向的底層集合會(huì)導(dǎo)致拋出異常,但是 Iterator 接口也有一個(gè) remove() 方法,它允許我們從底層集合中刪除 next() 剛剛返回的元素。調(diào)用 remove() 不會(huì)導(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 中剩余的所有元素,并為每個(gè)元素調(diào)用作為參數(shù)傳遞給 forEachRemaining() 的 Lambda 表達(dá)式。以下是使用 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 還包含一個(gè)名為 ListIterator 的接口,它擴(kuò)展了 Iterator 接口,表示雙向一個(gè)迭代器 - 意味著我們可以在迭代中向前和向后導(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());
}

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








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




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

在本節(jié)中,我將展示 Iterator 接口的一個(gè)超級(jí)簡(jiǎn)單的自定義實(shí)現(xiàn)。例子實(shí)現(xiàn)的比較粗糙,沒(méi)有考慮在迭代過(guò)程中檢測(cè)內(nèi)部元素是否被更改之類的問(wèn)題,但是足以讓你了解 Iterator 的實(shí)現(xiàn)應(yīng)該長(zhǎ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 接口里提供了默認(rèn)實(shí)現(xiàn),所以我們這個(gè)例子只提供了 hasNext() 和 next() 兩個(gè)方法的實(shí)現(xiàn)。這個(gè)實(shí)現(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 ,它表示可迭代對(duì)象的集合,這意味著,實(shí)現(xiàn) Iterable 接口的類可以迭代其元素。

Iterable 接口定義了三個(gè)方法,其中兩個(gè)提供了默認(rèn)實(shí)現(xiàn),只有 iterator() 方法是要求實(shí)現(xiàn)類必須實(shí)現(xiàn)的,該方法返回一個(gè)上個(gè)章節(jié)已經(jīng)介紹過(guò)的 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 接口擴(kuò)展了 Iterable 接口,所以集合框架里的 Collection 類族也都是 Iterable 的實(shí)現(xiàn)類--即他們的對(duì)象都可以迭代內(nèi)部元素。

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



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




下面的示例展示了如何通過(guò) Java 的 for-each 循環(huán)迭代 列表的元素。由于Java 的 List 接口擴(kuò)展了Collection 接口,而Collection 接口又?jǐn)U展了Iterable 接口,因此List 對(duì)象被 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() );
}



通過(guò)迭代器迭代Iterable對(duì)象




迭代  Iterable 對(duì)象元素的第二種方法是,通過(guò)調(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 對(duì)象




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

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

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

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



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




需要自己 Iterable 的需求很少,一般 集合框架里提供的數(shù)據(jù)結(jié)構(gòu)就能滿足我們的開(kāi)發(fā)需求了。不過(guò)為了演示,下面給出 Iterable 接口的一個(gè)超簡(jiǎn)單的自定義實(shí)現(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接口,兩者都跟集合對(duì)象的迭代有關(guān),相信大家在看 Java 提供的一些內(nèi)置對(duì)象、工具類方法的參數(shù)定義里見(jiàn)過(guò)這兩個(gè)從名字上看長(zhǎng)有點(diǎn)類似的接口類型。相信看過(guò)這篇文章,會(huì)減少你不少--“這兩塊貨都是啥?”的疑惑。

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





作者:碼出宇宙


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

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