在 Java 開發中,遍歷集合時刪除元素是高頻操作,但稍有不慎即拋出 ConcurrentModificationException。本文通過完整示例代碼,演示正確的遍歷刪除姿勢,並對比多種方案優劣,助你徹底掌握這一核心技能。
List list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
for (String s : list) { // 增強 for 底層使用 Iterator
if ("B".equals(s)) {
list.remove(s); // ❌ 直接操作集合,modCount 不一致
}
}
// 拋出 ConcurrentModificationException
根因:增強 for / 普通 Iterator 遍歷時,內部維護 `expectedModCount`;直接調用 `List.remove()` 修改 `modCount` 導致不匹配,觸發 fail-fast 機制。
List list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
Iterator it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if ("B".equals(s)) {
it.remove(); // ✅ 同步更新 expectedModCount
}
}
// 結果:[A, C, D]
ListIterator lit = list.listIterator();
while (lit.hasNext()) {
if ("B".equals(lit.next())) {
lit.remove(); // ✅ 同理安全
}
}
for (int i = list.size() - 1; i >= 0; i--) {
if ("B".equals(list.get(i))) {
list.remove(i); // ✅ 從後向前,索引不受影響
}
}
list.removeIf(s -> "B".equals(s)); // ✅ 內部實現同方案一
// 結果:[A, C, D]
List filtered = list.stream()
.filter(s -> !"B".equals(s))
.collect(Collectors.toList());
// 原列表不變,生成新列表
package com.example.collection;
import java.util.*;
public class ArrayListTraverseRemoveDemo {
public static void main(String[] args) {
// 準備數據
List students = new ArrayList<>();
students.add(new Student("小紅", 13, "白色"));
students.add(new Student("小明", 12, "黃色"));
students.add(new Student("小強", 123, "藍色"));
students.add(new Student("小李", 23, "白色"));
students.add(new Student("小愛", 4, "blue"));
students.add(new Student("小北", 2, "brave"));
System.out.println("=== 原始列表 ===");
printList(students);
// 方案一:Iterator.remove() 刪除姓名含"小"的學生(演示)
Iterator it = students.iterator();
while (it.hasNext()) {
Student s = it.next();
if (s.getName().contains("小")) { // 全部命中,僅演示
it.remove();
}
}
System.out.println("=== Iterator.remove() 後 ===");
printList(students); // 空列表
// 重置數據演示方案三:倒序索引刪除學號 > 100
students.addAll(Arrays.asList(
new Student("小紅", 13, "白色"),
new Student("小明", 12, "黃色"),
new Student("小強", 123, "藍色"),
new Student("小李", 23, "白色")
));
for (int i = students.size() - 1; i >= 0; i--) {
if (students.get(i).getNum() > 100) {
students.remove(i);
}
}
System.out.println("=== 倒序索引刪除 num>100 後 ===");
printList(students);
// 方案四:removeIf 刪除顏色為"白色"
students.removeIf(s -> "白色".equals(s.getColor()));
System.out.println("=== removeIf 刪除白色後 ===");
printList(students);
}
static void printList(List list) {
System.out.println("大小:" + list.size());
for (int i = 0; i < list.size(); i++) {
Student s = list.get(i);
System.out.printf("第%d個:姓名=%s, 學號=%d, 顏色=%s%n",
i + 1, s.getName(), s.getNum(), s.getColor());
}
System.out.println("------------");
}
static class Student {
private String name;
private int num;
private String color;
public Student(String name, int num, String color) {
this.name = name;
this.num = num;
this.color = color;
}
public String getName() { return name; }
public int getNum() { return num; }
public String getColor() { return color; }
}
}
=== 原始列表 ===
大小:6
第1個:姓名=小紅, 學號=13, 顏色=白色
第2個:姓名=小明, 學號=12, 顏色=黃色
第3個:姓名=小強, 學號=123, 顏色=藍色
第4個:姓名=小李, 學號=23, 顏色=白色
第5個:姓名=小愛, 學號=4, 顏色=blue
第6個:姓名=小北, 學號=2, 顏色=brave
------------
=== Iterator.remove() 後 ===
大小:0
------------
=== 倒序索引刪除 num>100 後 ===
大小:3
第1個:姓名=小紅, 學號=13, 顏色=白色
第2個:姓名=小明, 學號=12, 顏色=黃色
第3個:姓名=小李, 學號=23, 顏色=白色
------------
=== removeIf 刪除白色後 ===
大小:1
第1個:姓名=小明, 學號=12, 顏色=黃色
------------
| 場景 | 首選方案 | 備選 | 避坑提醒 |
|---|---|---|---|
| 通用 List、需邊刪邊做額外邏輯 | Iterator.remove() | ListIterator | 勿用增強 for 直接 remove |
| 僅 ArrayList、單純刪除、追求極致性能 | 倒序索引 for | removeIf() | LinkedList 禁用 |
| 簡單條件刪除、代碼簡潔優先 | removeIf() | Stream filter | 無法執行副作用邏輯 |
| 不可變風格、需保留原列表、並行處理 | Stream filter + collect | List.copyOf + removeIf | 額外內存開銷 |
| 需雙向遍歷/修改/獲取索引 | ListIterator | — | 同 Iterator 安全 |
掌握這 5 種姿勢,根據場景靈活選擇,即可從容應對所有集合遍歷刪除需求,徹底告別 ConcurrentModificationException。