震惊!遍历集合时直接调用remove()可能会出现问题!

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"]:

  1. 删除第一个元素"A":

    • 第一次循环删除"A"(modCount++)
    • 第二次循环调用next()时发现modCount变化 → 抛出异常
  2. 删除倒数第二个元素"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)写操作较慢 简单
相关推荐
花月C35 分钟前
Mysql-定时删除数据库中的验证码
数据库·后端·mysql·spring
XMYX-06 小时前
Spring Boot + Prometheus 实现应用监控(基于 Actuator 和 Micrometer)
spring boot·后端·prometheus
@yanyu6668 小时前
springboot实现查询学生
java·spring boot·后端
酷爱码8 小时前
Spring Boot项目中JSON解析库的深度解析与应用实践
spring boot·后端·json
AI小智9 小时前
Google刀刃向内,开源“深度研究Agent”:Gemini 2.5 + LangGraph 打造搜索终结者!
后端
java干货9 小时前
虚拟线程与消息队列:Spring Boot 3.5 中异步架构的演进与选择
spring boot·后端·架构
一只叫煤球的猫9 小时前
MySQL 8.0 SQL优化黑科技,面试官都不一定知道!
后端·sql·mysql
写bug写bug10 小时前
如何正确地对接口进行防御式编程
java·后端·代码规范
不超限10 小时前
Asp.net core 使用EntityFrame Work
后端·asp.net