removeIf 不会触发 ConcurrentModificationException,因为它不依赖外部迭代器,而是由集合内部原子执行“标记-整理”两阶段删除,全程只在末尾更新一次 modCount,避免了 modCount 与 expectedModCount 不一致。

Java 8 的 removeIf 方法在设计上绕开了 fail-fast 机制的触发路径,不是“规避”或“禁用”它,而是从源头避免了 modCount 与 expectedModCount 不一致的条件——它不依赖外部迭代器,也不暴露中间修改状态。
removeIf 为什么不会触发 ConcurrentModificationException
传统 for-each 或手动 Iterator 遍历时调用 list.remove(),会直接修改集合结构,导致 modCount++,但迭代器内部的 expectedModCount 没有同步更新,下一次 next() 就抛异常。而 removeIf 是集合自身提供的原子操作:
- 它内部使用自己的私有迭代逻辑(如 ArrayList 中是基于数组下标遍历),全程不对外暴露 Iterator 实例
- 所有删除动作由集合统一协调,在单次遍历中完成标记与移位,不中途多次调用 public remove() 方法
- modCount 只在最终批量收缩数组时递增一次,而不是每删一个就加一
removeIf 的执行过程其实是“延迟删除”
以 ArrayList 为例,removeIf 并非边遍历边物理删除,而是分两步:
- 第一遍扫描:记录所有满足条件的元素索引(或用位图标记)
- 第二遍整理:将未被标记的元素前移,最后一次性截断尾部冗余空间
这种模式天然避开“边读边改”的冲突场景,也就不需要校验 expectedModCount —— 因为整个过程没有对外发布的迭代器参与。
将后端 Java 接口文档转换为前端 TypeScript 类型定义(interface / type)。输入:飞书文档、Spring Boot 接口文档、Swagger/OpenAPI、手写 API 文档等。输出:包含请求参数与响应结果的 .md 文件,含完整 JSDoc 注释。触发关键词:Java 转 TS、接口转换、生成前端类型、接口文档转 TypeScript、DTO/VO/BO 转 interface、后端文档转前端类型、生成接口类型、类型定义生成。
立即学习“Java免费学习笔记(深入)”;
它仍然遵循 fail-fast 的设计哲学,只是换了实现方式
fail-fast 的本质不是“禁止删除”,而是“拒绝不一致的状态”。removeIf 没有违背这个原则,反而更严格:
- 如果传入的 Predicate 本身抛异常(比如空指针、除零),
removeIf会立即中断并向上抛出,不继续执行后续逻辑 - 它不允许在删除过程中被其他线程干扰:方法开始前会读取当前 modCount,若在执行中检测到外部修改(比如另一线程调用了 add),仍可能抛 ConcurrentModificationException
- 对只读集合(如 Collections.unmodifiableList)调用
removeIf,会直接抛 UnsupportedOperationException,而不是等到遍历中途才失败
对比其他安全删除方式的定位差异
removeIf 不是万能替代品,它的适用边界很明确:
- 适合单次条件过滤删除,语义清晰,代码简洁
- 不适合需要在删除时获取被删元素做额外处理(如日志、回调)的场景——Iterator.remove() 更合适
- 不适合大数据量且内存敏感的场景——它仍需遍历全量,且 ArrayList 的移动操作有 O(n²) 风险;此时 Stream.filter + collect 可能更可控
- 不适用于所有集合:LinkedList 支持,但某些自定义集合若未重写 removeIf,会回退到默认实现(基于 iterator.remove),仍可能触发异常

















