當咱們使用 for
或 while
循環來遍歷一個集合的元素,Iterator
容許咱們不用擔憂索引位置,甚至讓咱們不只僅是遍歷一個集合,同時還能夠改變它。例如,你若是要刪除循環中的元素,那麼 for
循環不見得老是可行的。java
結合自定義的迭代器,咱們能夠迭代更爲複雜的對象,以及向前和向後移動,而且知曉如何利用其優點也將變得很是清楚。安全
本文將深刻討論如何使用 Iterator
和 Iterable
接口。app
Iterator
接口用於迭代集合中的元素(List
,Set
或 Map
)。它用於逐個檢索元素,並在須要時針對每一個元素執行操做。框架
下面是用於遍歷集合與執行操做的方法: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複製代碼
正如你所看到的,蟻人已經從 復仇者聯盟
的名單中刪除了。
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複製代碼
Spliterator
接口在功能上與 Iterator
相同。你可能永遠不須要直接使用 Spliterator
,但讓咱們繼續討論一些用例。
可是,你應首先熟悉 Java Streams 和 Lambda Expressions in Java。
雖然咱們將列出 Spliterator
擁有的全部方法,可是 Spliterator
接口的所有工做超出了本文的範疇。咱們將經過一個例子討論 Spliterator
如何使用並行化更有效地遍歷咱們能夠分解的 Stream
。
咱們在處理 Spliterator
時使用的方法是:
: 返回該 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.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
// Getting Spliterator object on 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
// .trySplit() methodSpliterator
// 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複製代碼
本文中,咱們詳細討論瞭如何使用 Java 中的迭代器,甚至寫了一個定製的迭代器來探索 Iterable
接口的全部新的可能性。
Spliterator
接口對集合的遍歷進行內部優化。8月福利準時來襲,關注公衆號後臺回覆:003便可領取7月翻譯集錦哦~往期福利回覆:001,002便可領取!