Java 迭代接口:Iterator、ListIterator 和 Spliterator

1. 簡介

當咱們使用 forwhile 循環來遍歷一個集合的元素,Iterator 容許咱們不用擔憂索引位置,甚至讓咱們不只僅是遍歷一個集合,同時還能夠改變它。例如,你若是要刪除循環中的元素,那麼 for 循環不見得老是可行的。java

結合自定義的迭代器,咱們能夠迭代更爲複雜的對象,以及向前和向後移動,而且知曉如何利用其優點也將變得很是清楚。安全

本文將深刻討論如何使用 IteratorIterable 接口。app

2. Iterator()

Iterator 接口用於迭代集合中的元素(ListSetMap)。它用於逐個檢索元素,並在須要時針對每一個元素執行操做。框架

下面是用於遍歷集合與執行操做的方法:oop

  • .hasNext():若是尚未到達集合的末尾,則返回 true,不然返回 false
  • .next():返回集合中的下一個元素
  • .remove():從集合中移除迭代器返回的最後一個元素
  • .forEachRemaining():按順序爲集合中剩下的每一個元素執行給定的操做

首先,因爲迭代器是用於集合的,讓咱們作一個簡單的包含幾個元素的 ArrayList優化

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

// Now lets add some Avengers to the list
avengers.add("Ant-Man");
avengers.add("Black Widow");
avengers.add("Captain America");
avengers.add("Doctor Strange");複製代碼

咱們可使用一個簡單循環來遍歷這個集合:ui

System.out.println("Simple loop example:\n");
for (int i = 0; i < avengers.size(); i++) {
    System.out.println(avengers.get(i));
}複製代碼

不過,咱們想探索迭代器:this

System.out.println("\nIterator Example:\n");

// First we make an Iterator by calling 
// the .iterator() method on the collection
Iterator<String> avengersIterator = avengers.iterator();

// And now we use .hasNext() and .next() to go through it
while (avengersIterator.hasNext()) {
    System.out.println(avengersIterator.next());
}複製代碼

若是咱們想從這個 ArrayList 中刪除一個元素,會發生什麼?讓咱們試着使用常規的 for 循環:spa

System.out.println("Simple loop example:\n");
for (int i = 0; i < avengers.size(); i++) {
    if (avengers.get(i).equals("Doctor Strange")) {
        avengers.remove(i);
    }
    System.out.println(avengers.get(i));
}複製代碼

咱們會收到一個討厭的 IndexOutOfBoundsException翻譯

Simple loop example:

Ant-Man
Black Widow
Captain America
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 3, Size: 3複製代碼

這在遍歷集合時更改其大小是有意義的,加強 for 循環也同樣:

System.out.println("Simple loop example:\n");
for (String avenger : avengers) {
    if (avenger.equals("Doctor Strange")) {
        avengers.remove(avenger);
    }
    System.out.println(avenger);
}複製代碼

咱們再次收到了另外一個異常:

Simple loop example:

Ant-Man
Black Widow
Captain America
Doctor Strange
Exception in thread "main" java.util.ConcurrentModificationException複製代碼

這時迭代器就派上用場了,由它充當中間人,從集合中刪除元素,同時確保遍歷按計劃繼續:

Iterator<String> avengersIterator = avengers.iterator();
while (avengersIterator.hasNext()) {
    String avenger = avengersIterator.next();

    // First we must find the element we wish to remove
    if (avenger.equals("Ant-Man")) {
        // This will remove "Ant-Man" from the original
        // collection, in this case a List
        avengersIterator.remove();
    }
}複製代碼

這是保證在遍歷集合時刪除元素的安全方法。

並確認該元素是否已被刪除:

// We can also use the helper method .forEachRemaining()
System.out.println("For Each Remaining Example:\n");
Iterator<String> avengersIteratorForEach = avengers.iterator();

// This will apply System.out::println to all elements in the collection
avengersIteratorForEach.forEachRemaining(System.out::println);複製代碼

輸出以下:

For Each Remaining Example:

Black Widow
Captain America
Doctor Strange複製代碼

正如你所看到的,蟻人已經從 復仇者聯盟 的名單中刪除了。

2.1. ListIterator()

ListIterator 繼承自 Iterator 接口。它只在 List 上進行使用,能夠雙向迭代,這意味着你能夠從前到後或從後到前進行迭代。它也沒有 current 元素,由於遊標老是放在 List 的兩個元素之間,因此咱們用 .previous().next() 來訪問元素。

IteratorListIterator 之間有什麼區別呢?

首先,Iterator 能夠用於 任意集合 —— ListMapQueueSet 等。

ListIterator 只能應用於 List,經過添加這個限制,ListIterator 在方法方面能夠更加具體,所以,咱們引入了許多新方法,他們能夠幫助咱們在遍歷時對其進行修改。

若是你正在處理 List 實現(ArrayListLinkedList等),那麼使用 ListIterator 更爲可取一些。

下面是你可能會用到的方法:

  • .add(E e):向 List 中添加元素。
  • .remove():從 List 中刪除 .next().previous() 返回的最後一個元素。
  • .set(E e):使用指定元素來覆蓋 List .next().previous() 返回的最後一個元素。
  • .hasNext():若是尚未到達 List 的末尾,則返回 true,不然返回 false
  • .next():返回 List 中的下一個元素。
  • .nextIndex():返回下一元素的下標。
  • .hasPrevious():若是尚未到達 List 的開頭,則返回 true,不然返回 false
  • .previous():返回 List 的上一個元素。
  • .previousIndex():返回上一元素的下標。

再次,讓咱們用一些元素構成一個 ArrayList

ArrayList<String> defenders = new ArrayList<>();

defenders.add("Daredevil");
defenders.add("Luke Cage");
defenders.add("Jessica Jones");
defenders.add("Iron Fist");複製代碼

讓咱們用 ListIterator 來遍歷 List 並打印其元素:

ListIterator listIterator = defenders.listIterator(); 
  
System.out.println("Original contents of our List:\n");
while (listIterator.hasNext()) 
    System.out.print(listIterator.next() + System.lineSeparator()); 複製代碼

顯然,它的工做方式與 Iterator 相同。輸出以下:

Original contents of our List: 

Daredevil
Luke Cage
Jessica Jones
Iron Fist複製代碼

如今,讓咱們來嘗試修改一些元素:

System.out.println("Modified contents of our List:\n");

// Now let's make a ListIterator and modify the elements
ListIterator defendersListIterator = defenders.listIterator();

while (defendersListIterator.hasNext()) {
    Object element = defendersListIterator.next();
    defendersListIterator.set("The Mighty Defender: " + element);
}複製代碼

如今打印 List 的話會獲得以下結果:

Modified contents of our List:

The Mighty Defender: Daredevil
The Mighty Defender: Luke Cage
The Mighty Defender: Jessica Jones
The Mighty Defender: Iron Fist複製代碼

如今,讓咱們倒着遍歷列表,就像咱們能夠用 ListIterator 作的那樣:

System.out.println("Modified List backwards:\n");
while (defendersListIterator.hasPrevious()) {
    System.out.println(defendersListIterator.previous());
}複製代碼

輸出以下:

Modified List backwards:

The Mighty Defender: Iron Fist
The Mighty Defender: Jessica Jones
The Mighty Defender: Luke Cage
The Mighty Defender: Daredevil複製代碼

3. Spliterator()

Spliterator 接口在功能上與 Iterator 相同。你可能永遠不須要直接使用 Spliterator,但讓咱們繼續討論一些用例。

可是,你應首先熟悉 Java StreamsLambda Expressions in Java

雖然咱們將列出 Spliterator 擁有的全部方法,可是 Spliterator 接口的所有工做超出了本文的範疇。咱們將經過一個例子討論 Spliterator 如何使用並行化更有效地遍歷咱們能夠分解的 Stream

咱們在處理 Spliterator 時使用的方法是:

  • `
    .characteristics()
: 返回該 Spliterator 具備的做爲

   
複製代碼

int

值的特徵。 這些包括:

  - `ORDERED`
  - `DISTINCT`
  - `SORTED`
  - `SIZED`
  - `CONCURRENT`
  - `IMMUTABLE`
  - `NONNULL`
  - `SUBSIZED`

- `.estimateSize()`:返回遍歷做爲 `long` 值遇到的元素數量的估計值,若是沒法返回則返回 `long.MAX_VALUE`。

- `.forEachRemaining(E e)`:按順序對集合中的每一個剩餘元素執行給定操做。

- `.getComparator()`:若是該 `Spliterator` 的源是由 `Comparator` 排序的,其將返回 `Comparator`。

- `.getExactSizeIfKnown()`:若是大小已知則返回 `.estimateSize()`,不然返回 `-1`。

- `.hasCharacteristics(int characteristics)`:若是這個 `Spliterator` 的 `.characteristics()` 包含全部給定的特徵,則返回 `true`。

- `.tryAdvance(E e)`:若是存在剩餘元素,則對其執行給定操做,返回 `true`,不然返回 `false`。

- `.trySplit()`:若是這個 `Spliterator` 能夠被分區,返回一個 `Spliterator` 覆蓋元素,當從這個方法返回時,它將不被這個 `Spliterator` 覆蓋。

像往常同樣,讓咱們從一個簡單的 `ArrayList` 開始:
複製代碼

List mutants = new ArrayList<>();

mutants.add("Professor X");mutants.add("Magneto");mutants.add("Storm");mutants.add("Jean Grey");mutants.add("Wolverine");mutants.add("Mystique");

如今,咱們須要將 `Spliterator` 應用於 `Stream`。值得慶幸的是,因爲 Collections 框架,很容易在 `ArrayList` 和 `Stream` 之間進行轉換:
複製代碼

// Obtain a Stream to the mutants List.Stream mutantStream = mutants.stream();

// Getting Spliterator object on mutantStream.Spliterator mutantList = mutantStream.spliterator();

爲了展現其中的一些方法,讓咱們分別運行下它們:
複製代碼

// .estimateSize() methodSystem.out.println("Estimate size: " + mutantList.estimateSize());

// .getExactSizeIfKnown() methodSystem.out.println("nExact size: " + mutantList.getExactSizeIfKnown());

System.out.println("nContent of List:");// .forEachRemaining() methodmutantList.forEachRemaining((n) -> System.out.println(n));

// Obtaining another Stream to the mutant List.Spliterator splitList1 = mutantStream.spliterator();

// .trySplit() methodSpliterator splitList2 = splitList1.trySplit();

// If splitList1 could be split, use splitList2 first.if (splitList2 != null) {System.out.println("nOutput from splitList2:");splitList2.forEachRemaining((n) -> System.out.println(n));}

// Now, use the splitList1System.out.println("nOutput from splitList1:");splitList1.forEachRemaining((n) -> System.out.println(n));

咱們將獲得輸出:
複製代碼

Estimate size: 6

Exact size: 6

Content of List:Professor XMagnetoStormJean GreyWolverineMystique

Output from splitList2:Professor XMagnetoStorm

Output from splitList1:Jean GreyWolverineMystique

## 4. Iterable()

若是出於某種緣由,咱們想要建立一個自定義的 `Iterator` 接口,應該怎麼辦?你首先要熟悉的是這張圖:

![file](https://user-gold-cdn.xitu.io/2019/8/27/16cd06ab2be585b5?w=800&h=800&f=jpeg&s=54890)

要建立自定義 `Iterator`,咱們須要爲 `.hasNext()`、`.next()` 和 `.remove()` 作自定義實現。

在 `Iterator` 接口中,有一個方法,它返回一個集合中元素的迭代器,即 `.iterator()` 方法,還有一個方法爲迭代器中的每一個元素執行一個操做的方法,即 `.dorEach()` 方法。

例如,假設咱們是 Tony Stark,咱們須要寫個自定義迭代器來列出當前武器庫中的每件鋼鐵俠套裝。

首先,讓咱們建立一個類來獲取和設置 suit 數據:
複製代碼

public class Suit {

private String codename;
    private int mark;複製代碼
public Suit(String codename, int mark) {
        this.codename = codename;
        this.mark = mark;
    }複製代碼
public String getCodename() { return codename; }複製代碼
public int getMark() { return mark; }複製代碼
public void setCodename (String codename) {this.codename=codename;}複製代碼
public void setMark (int mark) {this.mark=mark;}複製代碼
public String toString() {
        return "mark: " + mark + ", codename: " + codename;
    }
}
```複製代碼

接下來讓咱們編寫自定義 Iterator:

// Our custom Iterator must implement the Iterable interface
public class Armoury implements Iterable<Suit> {
    
    // Notice that we are using our own class as a data type
    private List<Suit> list = null;

    public Armoury() {
        // Fill the List with data
        list = new LinkedList<Suit>();
        list.add(new Suit("HOTROD", 22));
        list.add(new Suit("SILVER CENTURION", 33));
        list.add(new Suit("SOUTHPAW", 34));
        list.add(new Suit("HULKBUSTER 2.0", 48));
    }
    
    public Iterator<Suit> iterator() {
        return new CustomIterator<Suit>(list);
    }

    // Here we are writing our custom Iterator
    // Notice the generic class E since we do not need to specify an exact class
    public class CustomIterator<E> implements Iterator<E> {
    
        // We need an index to know if we have reached the end of the collection
        int indexPosition = 0;
        
        // We will iterate through the collection as a List
        List<E> internalList;
        public CustomIterator(List<E> internalList) {
            this.internalList = internalList;
        }

        // Since java indexes elements from 0, we need to check against indexPosition +1
        // to see if we have reached the end of the collection
        public boolean hasNext() {
            if (internalList.size() >= indexPosition +1) {
                return true;
            }
            return false;
        }

        // This is our custom .next() method
        public E next() {
            E val = internalList.get(indexPosition);

            // If for example, we were to put here "indexPosition +=2" we would skip every 
            // second element in a collection. This is a simple example but we could
            // write very complex code here to filter precisely which elements are
            // returned. 
            // Something which would be much more tedious to do with a for or while loop
            indexPosition += 1;
            return val;
        }
        // In this example we do not need a .remove() method, but it can also be 
        // written if required
    }
}複製代碼

最後是 main 方法類:

public class IronMan {

    public static void main(String[] args) {

        Armoury armoury = new Armoury();

        // Instead of manually writing .hasNext() and .next() methods to iterate through 
        // our collection we can simply use the advanced forloop
        for (Suit s : armoury) {
            System.out.println(s);
        }
    }
}複製代碼

輸出以下:

mark: 22, codename: HOTROD
mark: 33, codename: SILVER CENTURION
mark: 34, codename: SOUTHPAW
mark: 48, codename: HULKBUSTER 2.0複製代碼

5. 總結

本文中,咱們詳細討論瞭如何使用 Java 中的迭代器,甚至寫了一個定製的迭代器來探索 Iterable 接口的全部新的可能性。

咱們還討論了 Java 是如何利用 Stream 的並行化,使用 Spliterator 接口對集合的遍歷進行內部優化。

8月福利準時來襲,關注公衆號​後臺回覆:003便可領取7月翻譯集錦哦~​往期福利回覆:001,002便可領取!

img

相關文章
相關標籤/搜索