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

Java ConcurrentModificationException 產生原因與四種解決方案(含 fail-fast 機制深度解析)

     編輯:Java 程式設計入門

現象

for (Person p : personList) {
    if (p.getGender() == Gender.MALE) {
        personList.remove(p); // 拋出 ConcurrentModificationException
    }
}

根本原因:fail-fast 迭代器機制

  • 集合建立迭代器時,記錄 modCount(修改次數)。
  • 迭代過程中,若偵測到 modCount 變化(集合結構被修改),立即拋出 ConcurrentModificationException。
  • 單執行緒也會觸發:直接呼叫集合的 remove/add/clear 而非迭代器的 remove()。

四種解決方案

1. 使用迭代器的 remove()(標準做法)

Iterator it = personList.iterator();
while (it.hasNext()) {
    Person p = it.next();
    if (p.getGender() == Gender.MALE) {
        it.remove(); // 安全移除,同步更新 modCount
    }
}

2. 反向索引迴圈移除(ArrayList 效能最佳)

for (int i = personList.size() - 1; i >= 0; i--) {
    if (personList.get(i).getGender() == Gender.MALE) {
        personList.remove(i); // 索引不受後續元素位移影響
    }
}

3. 先收集待刪除元素,再批次移除

List toRemove = new ArrayList<>();
for (Person p : personList) {
    if (p.getGender() == Gender.MALE) toRemove.add(p);
}
personList.removeAll(toRemove);

4. Java 8+ Stream 過濾收集新列表(函數式、不可變風格)

List filtered = personList.stream()
    .filter(p -> p.getGender() != Gender.MALE)
    .collect(Collectors.toList());
// 原列表不變,若需原地修改:personList.clear(); personList.addAll(filtered);

Map 遍歷同理

// ❌ 錯誤
for (String key : map.keySet()) {
    if (condition) map.remove(key);
}

// ✅ 正確:Iterator + Entry
Iterator> it = map.entrySet().iterator();
while (it.hasNext()) {
    if (condition) it.remove();
}

特殊情況:單元素集合不拋異常

當集合僅有 1 個元素且被移除後,迭代器 hasNext() 直接回傳 false,結束迴圈,未再檢查 modCount,故不拋異常。不可依賴此行為。

總結

方案優點缺點推薦場景
Iterator.remove()標準、語意清晰需顯式迭代器通用、教學
反向索引ArrayList 效能最好、無額外物件僅適用 RandomAccess 列表高效能、ArrayList
收集後批次刪除程式碼簡潔、邏輯分離額外 O(K) 空間業務邏輯複雜
Stream filter函數式、不可變、易平行化產生新列表、舊版本不支援Java 8+、新專案

核心原則

遍歷時修改結構,必須透過迭代器自身的修改方法,或改用不修改原集合的替代策略。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved