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

Java 集合刪除元素避坑指南:為什麼 for-each 會拋 ConcurrentModificationException

     編輯:Java 相關知識

問題現場

需求:從 source 列表中移除 blackNameList 包含的元素。初版代碼:

for (SharedBoardSmsWrapper item : source) {
    for (BlackNameListModel black : blackNameList) {
        if (item.getSource().equals(black.getSource())) {
            source.remove(item); // 拋出 ConcurrentModificationException
            break;
        }
    }
}

根因分析

  • for-each 編譯後使用 Iterator 遍歷。
  • Iterator 內部維護 expectedModCount,檢測到集合結構變更(如直接 list.remove())即拋出 ConcurrentModificationException(fail-fast 機制)。
  • 單線程下直接修改集合也會觸發,非多線程專屬。

錯誤嘗試:普通 for 循環

for (int i = 0; i < source.size(); i++) {
    // ...
    source.remove(item); // 索引錯位,導致漏刪或越界
}

原因:刪除元素後後續元素前移,索引 i 再自增會跳過下一個元素。

正解:Iterator.remove()

Iterator<SharedBoardSmsWrapper> it = source.iterator();
while (it.hasNext()) {
    SharedBoardSmsWrapper item = it.next();
    for (BlackNameListModel black : blackNameList) {
        if (item.getSource().equals(black.getSource())) {
            it.remove(); // 唯一合法的遍歷中刪除方式
            break;
        }
    }
}

原理解析

ArrayList.Itr 內部實現:

public void remove() {
    if (lastRet == -1) throw new IllegalStateException();
    checkForComodification();
    try {
        AbstractList.this.remove(lastRet); // 刪除最後訪問的元素
        if (lastRet < cursor) cursor--;    // 修正游標
        lastRet = -1;
        expectedModCount = modCount;       // 同步修改計數
    } catch (IndexOutOfBoundsException e) {
        throw new ConcurrentModificationException();
    }
}

關鍵點:

  1. 刪除 最後一次 next() 返回的元素(lastRet)。
  2. 刪除後 游標左移,expectedModCount 同步更新,避免 fail-fast 觸發。
  3. 每次 next() 只能調用一次 remove(),否則 IllegalStateException。

Java 8+ 更優雅的寫法

// 1. removeIf (單條件)
source.removeIf(item -> blackNameList.stream()
    .anyMatch(black -> item.getSource().equals(black.getSource())));

// 2. Stream 過濾生成新列表 (不修改原列表)
List<SharedBoardSmsWrapper> filtered = source.stream()
    .filter(item -> blackNameList.stream()
        .noneMatch(black -> item.getSource().equals(black.getSource())))
    .collect(Collectors.toList());

性能對比建議

場景推薦方式理由
單列表原地刪除、邏輯簡單Iterator.remove() / removeIf()O(1) 空間、代碼清晰
需保留原列表、或邏輯複雜Stream 過濾新建列表無副作用、易並行、函數式風格
雙重循環嵌套刪除外層 Iterator + 內層遍歷避免 ConcurrentModificationException

總結口訣

遍歷時要刪元素,Iterator.remove 最靠譜;for-each 別動底層集,普通 for 索引易亂;Java 8 有 removeIf,Stream 過濾更優雅。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved