for (E e : coll) 是 Java 5 引入的語法糖,編譯器將其展開為 Iterator<E> it = coll.iterator(); while (it.hasNext()) { e = it.next(); ... }。理解這點能解釋兩個經典問題。
// 編譯錯誤:for-each not applicable to expression type
// required: array or java.lang.Iterable
// found: CustomCollection<String>
for (String s : myCollection) { ... }
原因:CustomCollection 未實作 java.lang.Iterable<T> 介面。所有標準集合(Collection、List、Set)皆繼承 Iterable,故可直接使用。
修正:class CustomCollection<T> implements Iterable<T> { ... public Iterator<T> iterator() { return bucket.iterator(); } } 或繼承 AbstractCollection。
List<String> list = new ArrayList<>(Arrays.asList("A","B","C"));
// 寫法 1:顯式 Iterator + list.remove() → 拋 CME
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
it.next();
list.remove("A"); // 結構修改未透過 Iterator
}
// 寫法 2:for-each + list.remove() → 同樣拋 CME
for (String s : list) {
list.remove(s); // 底層仍用 Iterator.next() 檢測 modCount
}
根因:ArrayList 內部 modCount 記錄結構變更次數。Iterator.next() 每次檢查 expectedModCount == modCount,不符即拋 CME。list.remove() 修改 modCount 但 Iterator 不知情。
| 場景 | 正確寫法 |
|---|---|
| 遍歷時移除 | Iterator<E> it = list.iterator(); while(it.hasNext()) { if (cond) it.remove(); } |
| Java 8+ Stream 過濾 | list.removeIf(e -> cond); 或 list = list.stream().filter(e -> !cond).collect(toList()); |
| 需保留原列表 | List<E> filtered = new ArrayList<>(); for (E e: list) if (!cond) filtered.add(e); |
CopyOnWriteArrayList 迭代器不拋 CME(適合讀多寫少),但記憶體開銷大。T[] 同樣適用,編譯器展開為索引迴圈,無 Iterator、無 CME,但陣列長度固定無法移除。