1. Iterable 介面
在 Java 中,幾乎所有集合(除了 Map)都實作了介面 Iterable。這表示你可以依序遍歷它們——一個元素接著一個元素,而不必深入其內部結構的細節。對程式設計師而言,這就像是「集合自帶一種走訪所有元素的方式」。
介面 Iterable 只定義了一個方法:
Iterator<E> iterator();
方法 iterator() 會回傳一個型別為 Iterator 的物件——一個知道如何一步一步走遍集合的「助手」。因此,熟悉的 for-each 迴圈才能運作:
for (ElementType e : collection) {
// ...
}
其背後正是那個 Iterator 在運作。額外好處:透過它可以在遍歷期間安全地用 remove() 刪除元素。若用一般遍歷直接刪除,很容易遇到 ConcurrentModificationException。
2. Iterator 介面
Iterator 就像一位「快遞員」,能沿著你的集合前進,不遺漏元素,並遵守其既定的遍歷順序。
| 方法 | 說明 |
|---|---|
|
還有可遍歷的元素嗎? |
|
回傳下一個元素並移動到它 |
|
安全刪除目前元素,不會出錯 |
透過 Iterator 遍歷集合的範例
import java.util.*;
public class IteratorDemo {
public static void main(String[] args) {
List<String> tasks = new ArrayList<>();
tasks.add("撫摸貓咪");
tasks.add("做功課");
tasks.add("看影集");
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
System.out.println("任務: " + task);
}
}
}
這裡發生了什麼?
- 透過 tasks.iterator() 取得迭代器。
- 只要還有下一個元素(hasNext() 回傳 true),就透過 next() 取得並輸出它。
- Iterator 會自行維持遍歷順序——你不需要知道集合在內部如何存放元素。
3. 既然有各種迴圈,為什麼還需要 Iterator?
使用 Iterator 可以遍歷任何集合,即使它沒有索引(例如,Set)。這是一種不依賴具體集合型別的通用方式。
安全地刪除元素
常見任務:遍歷集合並刪除某些元素。若用 for-each 這麼做,可能會出錯:
for (String task : tasks) {
if (task.contains("貓")) {
tasks.remove(task); // 砰!ConcurrentModificationException
}
}
為什麼會這樣? 集合並不預期在由迭代器發起的遍歷過程中,還會被直接修改其結構。
正確作法:
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
if (task.contains("貓")) {
it.remove(); // 一切都會很順利!
}
}
為什麼不能只用索引?
因為不是所有集合都有索引。比方說,HashSet 或 TreeSet 就沒有所謂「第五個元素」這種概念。Iterator 總是能運作——這就是它的強項。
4. 關於 for-each 的細節
增強型 for(for-each)在 Java 5 就已出現。它本質上是語法糖,讓你能以最簡單的方式遍歷元素:
for (String task : tasks) {
System.out.println("任務: " + task);
}
在底層,編譯器會呼叫 iterator(),透過 hasNext() 檢查元素,再用 next() 取出它們。從語意上幾乎就像一句人話:「對於清單中的每一個任務」。
什麼時候不適合用 for-each?
- 需要在遍歷期間刪除元素(for-each 無法直接呼叫 remove())。
- 需要存取索引,例如想依位置替換元素。
- 你在操作 Map——它是「鍵-值」配對,遍歷需要專屬的做法。
5. 遍歷 Map:技巧與細節
介面 Map 並未直接實作 Iterable,因為它是「鍵-值」配對的集合。不過,Map 提供了便於遍歷的多種視圖。
按鍵遍歷
Map<String, String> users = new HashMap<>();
users.put("vasya", "vasya@example.com");
users.put("petya", "petya@gmail.com");
for (String login : users.keySet()) {
System.out.println("帳號: " + login);
}
按值遍歷
for (String email : users.values()) {
System.out.println("Email: " + email);
}
按配對(鍵-值)遍歷
最通用的方式——遍歷 entrySet():
for (Map.Entry<String, String> entry : users.entrySet()) {
System.out.println("帳號: " + entry.getKey() + ", Email: " + entry.getValue());
}
有趣的事實: Entry 是 Map 的內部介面,提供 getKey() 與 getValue() 方法。這樣你就能同時取得配對的兩個部分。
透過 Iterator 遍歷
Iterator<Map.Entry<String, String>> it = users.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
// 甚至可以安全刪除元素:
if (entry.getKey().startsWith("v")) {
it.remove();
}
}
6. 真實情境:遍歷集合如何幫助你的應用程式
範例:列出使用者的所有任務
List<String> tasks = new ArrayList<>();
tasks.add("做功課");
tasks.add("撫摸貓咪");
tasks.add("看影集");
System.out.println("您今天的任務:");
for (String task : tasks) {
System.out.println("- " + task);
}
現在刪除所有包含 "貓" 這個字的任務:
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
if (task.contains("貓")) {
it.remove();
}
}
System.out.println("剩下的任務:");
for (String task : tasks) {
System.out.println("- " + task);
}
範例:透過 Set 遍歷唯一的登入帳號
Set<String> logins = new HashSet<>();
logins.add("vasya");
logins.add("petya");
logins.add("masha");
for (String login : logins) {
System.out.println("使用者: " + login);
}
請注意:Set 的輸出順序可能是任意的!
範例:遍歷 Map 來顯示使用者
Map<String, String> users = new HashMap<>();
users.put("vasya", "vasya@example.com");
users.put("petya", "petya@gmail.com");
for (Map.Entry<String, String> entry : users.entrySet()) {
System.out.println("帳號: " + entry.getKey() + ", Email: " + entry.getValue());
}
7. Iterator.remove():安全刪除元素
初學者最常見的錯誤之一——在 for-each 遍歷時嘗試刪除集合中的元素。迭代器透過 remove() 解決了這個問題。
它怎麼運作?
- 呼叫 it.remove() 時會刪除「目前元素」——也就是最近一次 next() 回傳的那個。
- 這是安全的:集合不會拋出 ConcurrentModificationException。
範例:
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
int n = it.next();
if (n % 2 == 0) {
it.remove(); // 刪除所有偶數
}
}
System.out.println(numbers); // [1, 3, 5]
集合遍歷示意圖
+---------+ +---------+ +---------+
| Element | --> | Element | --> | Element | ...
+---------+ +---------+ +---------+
^ ^
| |
next() next()
Iterator 會「一步一步」走過元素,直到 hasNext() 回傳 false 為止。
9. 使用 Iterator 與遍歷集合時的常見錯誤
錯誤一:在 for-each 遍歷時修改集合。
直接在 for-each 中刪除元素會導致 ConcurrentModificationException:
for (String task : tasks) {
if (task.contains("貓")) {
tasks.remove(task); // 砰!ConcurrentModificationException
}
}
請使用 Iterator 以及它的 remove()。
錯誤二:在 remove() 之前未先呼叫 next()。
必須先透過 next() 取得目前元素,否則迭代器不知道要刪除哪一個。
Iterator<String> it = tasks.iterator();
it.remove(); // 錯誤!必須先呼叫 next()
錯誤三:嘗試在 for-each 中直接遍歷 Map。
Map 並未直接實作 Iterable——請使用 keySet()、values() 或 entrySet()。
Map<String, String> users = new HashMap<>();
// for (String entry : users) { ... } // 錯誤:不能這樣做
for (Map.Entry<String, String> e : users.entrySet()) {
// 正確
}
錯誤四:用迭代器遍歷時,在迭代器之外修改集合。
遍歷期間只能透過 it.remove() 刪除元素,而不是呼叫集合的方法。
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
if (task.contains("貓")) {
tasks.remove(task); // 錯誤!應該使用 it.remove()
}
}
GO TO FULL VERSION