震惊!遍历集合时直接调用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)写操作较慢 简单
相关推荐
Victor35641 分钟前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易41 分钟前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
Kiri霧1 小时前
Range循环和切片
前端·后端·学习·golang
WizLC1 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Victor3561 小时前
Netty(19)Netty的性能优化手段有哪些?
后端
爬山算法1 小时前
Netty(15)Netty的线程模型是什么?它有哪些线程池类型?
java·后端
白宇横流学长2 小时前
基于SpringBoot实现的冬奥会科普平台设计与实现【源码+文档】
java·spring boot·后端
Python编程学习圈2 小时前
Asciinema - 终端日志记录神器,开发者的福音
后端
bing.shao2 小时前
Golang 高并发秒杀系统踩坑
开发语言·后端·golang
壹方秘境2 小时前
一款方便Java开发者在IDEA中抓包分析调试接口的插件
后端