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

Java 集合迭代器 Iterator.remove() 陷阱:List 與 Set 的行為差異深度剖析

     編輯:Java 相關知識

現象觀察

使用 Iterator 遍歷集合時調用集合自身的 remove()(而非 iterator.remove()),ArrayList 和 TreeSet 表現出截然不同的拋出 ConcurrentModificationException 的時機。

代碼復現

// ArrayList 場景
List list = new ArrayList<>(Arrays.asList("aaa", "bbb", "ccc"));
Iterator it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("bbb")) { // 遍歷到倒數第二個元素
        list.remove("ccc"); // 刪除最後一個元素
    }
} // 不拋出異常!

// TreeSet 場景
Set set = new TreeSet<>(Arrays.asList("ddd", "eee", "fff"));
Iterator it1 = set.iterator();
while (it1.hasNext()) {
    if (it1.next().equals("fff")) { // 遍歷到最後一個元素
        set.remove("eee"); // 刪除中間元素
    }
} // 拋出 ConcurrentModificationException!

源碼級原理剖析

ArrayList 的 Itr 實現

  • 維護 expectedModCount 快照,cursor 指向下一個待返回元素索引,lastRet 記錄上一次返回的索引。
  • next():檢查 modCount,返回 elementData[cursor++],更新 lastRet。
  • remove()(Iterator 自己的):調用 ArrayList.remove(lastRet),cursor--,lastRet = -1,同步更新 expectedModCount = modCount。
  • 集合自身 remove(Object):修改 modCount,數組元素前移。

關鍵點: 當遍歷到倒數第二個元素(cursor = size-1)時,集合刪除最後一個元素(index = size-1),導致數組長度減 1,但 cursor 仍指向原 size-1 位置,此時 cursor == size(新 size),hasNext() 返回 false,循環正常結束,下一次 next() 不會被調用,故不檢查 modCount,不拋異常。

TreeSet 的 Iterator 實現

  • 基於 NavigableMap,內部使用 TreeMap 的 PrivateEntryIterator。
  • 維護 next 指向下一個節點,lastReturned 記錄上一個節點。
  • next():先檢查 modCount,然後 lastReturned = next,next = successor(next)。
  • 集合自身 remove(Object):修改 modCount,紅黑樹結構調整,可能導致 successor 關係變化。

關鍵點: 遍歷到最後一個元素時,next() 已將 lastReturned 指向該元素,next 置為 null。隨後集合刪除其他元素導致 modCount 變化,下一次循環進入 hasNext() 不檢查,但再次調用 next() 時(若有)會檢查 modCount 拋異常。實測中因 hasNext() 為 false 循環結束,但若繼續調用 next() 則拋異常。

核心結論

  1. 絕對禁忌: 迭代過程中**絕不可**直接調用集合的 remove()、`add()`、`clear()` 等結構修改方法。
  2. 正確姿勢: 必須使用 iterator.remove(),它會同步更新迭代器內部的 expectedModCount。
  3. 行為差異源於實現細節: ArrayList 基於數組索引,TreeSet 基於樹節點鏈接,對併發修改的敏感點不同。
  4. JDK 文檔明確聲明: 除非調用迭代器自身的 remove(),否則在迭代器創建後以任何方式修改集合結構,迭代器行為都是**未定義**的。

最佳實踐

// 正確刪除符合條件的元素
Iterator it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (shouldRemove(s)) {
        it.remove(); // 唯一安全方式
    }
}

// Java 8+ 函數式風格(內部同樣使用迭代器)
list.removeIf(this::shouldRemove);
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved