不能在 for-each 循环中直接调用 list.remove(),因为其底层使用 Iterator 且未暴露引用,直接调用会触发 ConcurrentModificationException;必须用 Iterator.remove()(需紧跟 next() 后调用)或 JDK 8+ 的 removeIf()。

为什么不能在 for-each 循环里直接调用 list.remove()
因为 for-each 底层用的是 Iterator,但你没拿到引用,所以无法控制删除时机。一旦在循环体里对 ArrayList 或 LinkedList 调用 list.remove(),会触发 ConcurrentModificationException —— 这不是并发问题,而是 modCount 和 expectedModCount 不匹配导致的快速失败机制。
常见错误写法:
for (String s : list) {
if (s.startsWith("tmp")) {
list.remove(s); // ⚠️ 抛出 ConcurrentModificationException
}
}
必须用 Iterator.remove(),且只能紧跟在 next() 之后调用
Iterator.remove() 是唯一被设计为“边遍历边删”的安全方式,但它有严格调用时序要求:必须在调用 next() 之后、且在下一次 next() 之前调用,否则抛 IllegalStateException。
- ✅ 正确顺序:
it.next()→it.remove() - ❌ 错误顺序:
it.remove()(没调过next),或it.next()→it.next()→it.remove() - ⚠️ 注意:
remove()删除的是上一次next()返回的元素,不是当前判断的“条件值”
实操示例:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next(); // 必须先取值
if (s.startsWith("tmp")) {
it.remove(); // ✅ 安全删除
}
}
remove() 在不同集合上的行为差异
虽然接口统一,但底层实现影响性能和语义:
-
ArrayList.remove()通过数组复制移动后续元素,时间复杂度 O(n),频繁删除建议改用CopyOnWriteArrayList(仅适用于读多写少) -
LinkedList.remove()是 O(1),因为只改指针,适合高频增删 -
HashSet/TreeSet的Iterator.remove()是安全的,但注意:删除后迭代器仍可继续,不会跳过下一个元素 -
ConcurrentHashMap不支持Iterator.remove()(会抛UnsupportedOperationException),得用computeIfPresent或remove(key)
替代方案:removeIf() 更简洁,但要注意 JDK 版本和副作用
JDK 8+ 提供了 Collection.removeIf(Predicate),本质也是内部用 Iterator + remove() 实现,语义清晰、代码短:
list.removeIf(s -> s.startsWith("tmp")); // ✅ 等价于手动 while + it.remove()
但要注意:
- 它是一次性遍历执行,无法在删除过程中做额外逻辑(比如记录被删元素、触发回调)
- 对
ArrayList,它比手写Iterator略快(避免了每次next()的边界检查开销) - 某些自定义
Collection子类可能未重写removeIf,回退到默认实现(效率低)
真正容易被忽略的是:如果 Predicate 本身有状态或副作用(比如修改外部变量、IO),在并行流或多次调用时行为可能出人意料 —— 它只保证“当前遍历中”的执行顺序,不提供线程安全保证。

















