csharp
我们在使用Java集合时,经常会遇到这样的代码:
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
for (String s : list) {
if (s.equals("B")) {
list.remove(s); // 直接调用remove
}
}
按照Java规范,这段代码应该抛出ConcurrentModificationException
,但实际运行时,有时却不会抛出异常。这是为什么呢?
底层机制分析
1. modCount机制
ArrayList内部维护了一个modCount
变量,记录结构性修改次数:
protected transient int modCount = 0; // 修改计数器
每次执行add()
、remove()
等操作时,modCount
都会递增。
2. Iterator的检查机制
增强for循环实际上使用了Iterator,其内部会记录初始的modCount
:
int expectedModCount = modCount; // 创建Iterator时记录
每次调用next()
方法时,会检查:
java
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
为什么有时不抛出异常?
关键点在于:Iterator只在调用next()时检查modCount
特殊情况分析
假设list初始为["A", "B", "C"]:
-
删除第一个元素"A":
- 第一次循环删除"A"(modCount++)
- 第二次循环调用next()时发现modCount变化 → 抛出异常
-
删除倒数第二个元素"B":
- 第一次循环删除"B"(modCount++)
- 第二次循环时hasNext()发现已到末尾 → 不调用next() → 不检查modCount → 不抛异常
正确做法
1. 使用Iterator.remove()
java
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("B")) {
it.remove(); // 正确方式
}
}
2. 使用removeIf()(Java8+)
list.removeIf(s -> s.equals("B"));
3. 创建新列表暂存待删元素(通用方法)
scss
List<String> toRemove = new ArrayList<>();
for (String s : list) {
if (s.equals("B")) {
toRemove.add(s);
}
}
list.removeAll(toRemove); // 最后统一删除
4. 使用CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>(); // 可以安全地在遍历时修改
方法对比
方式 | 安全性 | 适用场景 | 性能 | 代码复杂度 |
---|---|---|---|---|
直接remove() | ❌ | 无 | - | 简单但不安全 |
Iterator.remove() | ✅ | 单线程 | O(n) | 中等 |
removeIf() | ✅ | Java8+ | O(n) | 最简单 |
暂存后删除 | ✅ | 所有版本 | O(2n) | 较复杂 |
CopyOnWriteArrayList | ✅ | 多线程 | O(n)写操作较慢 | 简单 |