1. 簡介 當我們使用 或 迴圈來遍歷一個集合的元素, 允許我們不用擔心索引位置,甚至讓我們不僅僅是遍歷一個集合,同時還可以改變它。例如,你如果要刪除迴圈中的元素,那麼 迴圈不見得總是可行的。 結合自定義的迭代器,我們可以迭代更為複雜的對象,以及向前和向後移動,並且知曉如何利用其優勢也將變得非常清楚 ...
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即可領取!