首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java for-each 迴圈兩大隱藏坑:Iterable 契約與 ConcurrentModificationException

     編輯:Java 相關知識

語法糖背後的編譯器魔法與執行期陷阱

for (E e : coll) 是 Java 5 引入的語法糖,編譯器將其展開為 Iterator<E> it = coll.iterator(); while (it.hasNext()) { e = it.next(); ... }。理解這點能解釋兩個經典問題。

坑 1:自訂集合無法直接用 for-each

// 編譯錯誤: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。

坑 2:for-each 中移除元素拋 ConcurrentModificationException

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(適合讀多寫少),但記憶體開銷大。
  • for-each 對陣列 T[] 同樣適用,編譯器展開為索引迴圈,無 Iterator、無 CME,但陣列長度固定無法移除。
  • 語法糖定義:不增加語言表達能力,但提升可讀性、減少錯誤的語法簡寫(如泛型擦除、自動拆裝箱、增強 for、try-with-resources、Lambda)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved