Java 迭代介面:Iterator、ListIterator 和 Spliterator
- 2019 年 10 月 3 日
- 筆記
1. 簡介
當我們使用 for
或 while
循環來遍歷一個集合的元素,Iterator
允許我們不用擔心索引位置,甚至讓我們不僅僅是遍歷一個集合,同時還可以改變它。例如,你如果要刪除循環中的元素,那麼 for
循環不見得總是可行的。
結合自定義的迭代器,我們可以迭代更為複雜的對象,以及向前和向後移動,並且知曉如何利用其優勢也將變得非常清楚。
本文將深入討論如何使用 Iterator
和 Iterable
介面。
2. Iterator()
Iterator
介面用於迭代集合中的元素(List
,Set
或 Map
)。它用於逐個檢索元素,並在需要時針對每個元素執行操作。
下面是用於遍歷集合與執行操作的方法:
.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");
我們可以使用一個簡單循環來遍歷這個集合:
System.out.println("Simple loop example:n"); for (int i = 0; i < avengers.size(); i++) { System.out.println(avengers.get(i)); }
不過,我們想探索迭代器:
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
循環:
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()
來訪問元素。
Iterator
和ListIterator
之間有什麼區別呢?
首先,Iterator
可以用於 任意集合 —— List
、Map
、Queue
、Set
等。
ListIterator
只能應用於 List,通過添加這個限制,ListIterator
在方法方面可以更加具體,因此,我們引入了許多新方法,他們可以幫助我們在遍歷時對其進行修改。
如果你正在處理 List
實現(ArrayList
、LinkedList
等),那麼使用 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 Streams 和 Lambda 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<String> 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<String> mutantStream = mutants.stream(); // Getting Spliterator object on mutantStream. Spliterator<String> mutantList = mutantStream.spliterator();
為了展示其中的一些方法,讓我們分別運行下它們:
// .estimateSize() method System.out.println("Estimate size: " + mutantList.estimateSize()); // .getExactSizeIfKnown() method System.out.println("nExact size: " + mutantList.getExactSizeIfKnown()); System.out.println("nContent of List:"); // .forEachRemaining() method mutantList.forEachRemaining((n) -> System.out.println(n)); // Obtaining another Stream to the mutant List. Spliterator<String> splitList1 = mutantStream.spliterator(); // .trySplit() method Spliterator<String> 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 splitList1 System.out.println("nOutput from splitList1:"); splitList1.forEachRemaining((n) -> System.out.println(n));
我們將得到輸出:
Estimate size: 6 Exact size: 6 Content of List: Professor X Magneto Storm Jean Grey Wolverine Mystique Output from splitList2: Professor X Magneto Storm Output from splitList1: Jean Grey Wolverine Mystique
4. Iterable()
如果出於某種原因,我們想要創建一個自定義的 Iterator
介面,應該怎麼辦?你首先要熟悉的是這張圖:
要創建自定義 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即可領取!