CodeGym /課程 /JAVA 25 SELF /Iterable 與 Iterator:遍歷集合

Iterable 與 Iterator:遍歷集合

JAVA 25 SELF
等級 27 , 課堂 1
開放

1. Iterable 介面

在 Java 中,幾乎所有集合(除了 Map)都實作了介面 Iterable。這表示你可以依序遍歷它們——一個元素接著一個元素,而不必深入其內部結構的細節。對程式設計師而言,這就像是「集合自帶一種走訪所有元素的方式」。

介面 Iterable 只定義了一個方法:

Iterator<E> iterator();

方法 iterator() 會回傳一個型別為 Iterator 的物件——一個知道如何一步一步走遍集合的「助手」。因此,熟悉的 for-each 迴圈才能運作:

for (ElementType e : collection) {
    // ...
}

其背後正是那個 Iterator 在運作。額外好處:透過它可以在遍歷期間安全地用 remove() 刪除元素。若用一般遍歷直接刪除,很容易遇到 ConcurrentModificationException

2. Iterator 介面

Iterator 就像一位「快遞員」,能沿著你的集合前進,不遺漏元素,並遵守其既定的遍歷順序。

方法 說明
boolean hasNext()
還有可遍歷的元素嗎?
E next()
回傳下一個元素並移動到它
void remove()
安全刪除目前元素,不會出錯

透過 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(); // 一切都會很順利!
    }
}

為什麼不能只用索引?

因為不是所有集合都有索引。比方說,HashSetTreeSet 就沒有所謂「第五個元素」這種概念。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());
}

有趣的事實: EntryMap 的內部介面,提供 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()
    }
}
1
任務
JAVA 25 SELF, 等級 27, 課堂 1
上鎖
外星語言辭典 👽
外星語言辭典 👽
1
任務
JAVA 25 SELF, 等級 27, 課堂 1
上鎖
專案技術清單管理 🛠️
專案技術清單管理 🛠️
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION