Java遍历集合时删除元素抛出异常ConcurrentModificationException的排查与解决

 更新时间:2026年08月31日 08:52:38   作者:tianyu234  
Java遍历集合时删除元素,你是否遇到过 ConcurrentModificationException异常,本文从Iterator接口设计、fail-fast底层原理到ListIterator双向遍历,再到自定义迭代器实战,帮你彻底搞懂Java迭代器机制,掌握安全删除元素、避免并发修改异常的实用技巧

从 ConcurrentModificationException 到 fail-safe,从 ListIterator 到自定义迭代器,一篇带你真正掌握迭代器

我们在日常开发中,几乎天天都在遍历集合。但你有没有想过:

  • 为什么在遍历 ArrayList 时,用 list.remove() 会抛异常,但用 iterator.remove() 却没事?
  • 为什么 ConcurrentHashMap 的迭代器不会抛异常?
  • Iterator 和 ListIterator 到底有什么区别?

这些问题的答案,都藏在 迭代器(Iterator) 的设计哲学里。今天这篇文章,我们从接口设计、快速失败(fail-fast)机制、双向遍历(ListIterator)、自定义实现到生产级避坑,把 Iterator 彻底讲透。

一、先上结论(一张表看懂核心角色)

角色接口/类核心职责关键方法特点
可迭代对象Iterable<T>返回迭代器(被遍历的对象)iterator()增强 for 循环的“入场券”
迭代器(单向)Iterator<E>单向遍历并安全删除hasNext()、next()、remove()最基础、最通用
迭代器(双向)ListIterator<E>双向遍历,支持增/改hasPrevious()、previous()、add()、set()仅限 List 及其子类
拆分迭代器Spliterator<E>并行遍历(Java 8+)trySplit()、forEachRemaining()Stream 并行流的底层

一句话记忆口诀:

  • Iterable → 容器本身(能产生迭代器)。
  • Iterator → 单向箭头(只能往前走,能删)。
  • ListIterator → 双向箭头(能前能后,能增能改)。
  • Spliterator → 多箭头(分块并行)。

二、Iterator基础:为什么不用普通for循环?

1. 三种遍历方式对比

List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));

// 方式1:普通 for 循环(依赖下标)
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 方式2:增强 for 循环(语法糖,底层也是迭代器)
for (String s : list) {
    System.out.println(s);
}

// 方式3:显式迭代器(最灵活)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    System.out.println(s);
}

2. 为什么需要迭代器?(解决了什么问题)

  1. 统一遍历接口:无论 List、Set、Queue,遍历方式完全一致(解耦数据结构)。
  2. 安全的删除操作:普通 for 循环删除元素会导致索引错乱;增强 for 循环直接删除会抛异常;只有 Iterator.remove() 才是安全的。
  3. 隐藏底层实现:你不需要关心是数组还是链表,只管 hasNext() + next()。

三、核心方法详解(hasNext、next、remove)

1.hasNext()和next()(遍历的基本动作)

Iterator<String> it = list.iterator();
while (it.hasNext()) {   // 1. 问:还有吗?
    String s = it.next(); // 2. 拿:拿到下一个,并且游标后移
    System.out.println(s);
}

注意:如果没有调用 hasNext() 直接调用 next(),当没有元素时会抛出 NoSuchElementException。

2.remove()(唯一安全的删除方式)

这是迭代器最核心的实用价值。

List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));

// ✅ 正确:使用迭代器删除偶数
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
    Integer num = it.next();
    if (num % 2 == 0) {
        it.remove();  // 安全删除当前元素
    }
}
System.out.println(numbers);  // [1, 3, 5]

3. remove()的两条铁律(面试常问)

  1. 每次 next() 后只能调用一次 remove()。
  2. 必须先调用 next(),再调用 remove()(不能连续删除,也不能在 next() 之前删除)。
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    // it.remove();  // ❌ 报错!IllegalStateException(没有先 next)
    Integer num = it.next();
    it.remove();
    // it.remove();  // ❌ 报错!IllegalStateException(连续删除两次)
}

四、增强for循环的“甜蜜陷阱”(重点)

语法糖:for (String s : list) 编译后就是 while (it.hasNext()) { String s = it.next(); ... }。

致命错误:在增强 for 循环中直接调用 list.remove()。

List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));

// ❌ 错误!抛出 ConcurrentModificationException
for (String s : list) {
    if ("B".equals(s)) {
        list.remove(s);  // 直接操作集合,导致迭代器检测到 modCount 变化
    }
}

为什么报错?

因为增强 for 底层隐式创建了迭代器。集合内部有一个 modCount 计数器(记录修改次数)。list.remove() 会修改 modCount,但迭代器内部的 expectedModCount 没有同步更新,导致两者不相等,迭代器检测到后立即抛出 ConcurrentModificationException(快速失败机制,见下一节)。

正确做法:使用显式 Iterator 调用 it.remove()。

五、ListIterator:双向迭代器(支持增、改)

ListIterator 是 Iterator 的子接口,专门为 List 提供,支持双向遍历和元素修改/添加。

1.ListIteratorvsIterator对比

功能IteratorListIterator
方向只能从前到后双向(前/后)
删除✅ remove()✅ remove()
添加❌ 不支持✅ add(E e)
修改❌ 不支持✅ set(E e)
获取索引❌ 不支持✅ nextIndex() / previousIndex()
适用范围Collection 所有子类仅限 List(含 ArrayList、LinkedList)

2. 使用示例(双向遍历 + 添加)

List<String> list = new ArrayList<>(Arrays.asList("A", "B", "D"));

ListIterator<String> lit = list.listIterator();
// 1. 从前往后遍历
while (lit.hasNext()) {
    String s = lit.next();
    if ("B".equals(s)) {
        lit.add("C");   // 在 B 后面插入 C(游标自动跳过新元素)
    }
}
System.out.println(list);  // [A, B, C, D]

// 2. 从后往前遍历
while (lit.hasPrevious()) {
    String s = lit.previous();
    if ("C".equals(s)) {
        lit.set("X");   // 修改当前元素为 X
    }
}
System.out.println(list);  // [A, B, X, D]

核心机制:add() 插入的元素位于当前游标之前(即 next() 返回的元素之前)。set() 更新的是上一次 next() 或 previous() 返回的元素。

六、ConcurrentModificationException:快速失败(fail-fast)机制

1. 什么是 fail-fast?

Java 集合(ArrayList、HashMap 等)的迭代器在设计时采用了快速失败机制:当迭代器被创建后,如果集合内容被非迭代器方式(如直接 list.remove())修改,迭代器会立即抛出 ConcurrentModificationException,而不是冒着风险继续执行。

2. 底层原理(源码视角)

// AbstractList 源码片段
public abstract class AbstractList<E> {
    protected transient int modCount = 0;  // 集合结构修改次数
    
    // 迭代器内部
    private class Itr implements Iterator<E> {
        int expectedModCount = modCount;  // 创建迭代器时保存的快照
        
        public E next() {
            checkForComodification();  // 每次 next 都检查
            // ...
        }
        
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
}

当 list.remove() 被调用时,modCount++;但迭代器的 expectedModCount 没变。下次调用 it.next() 时发现两者不等,直接抛异常。

3. 如何正确避免?

  • 单线程:使用 Iterator.remove()(它会同步更新 expectedModCount)。
  • 多线程:使用 ConcurrentHashMap、CopyOnWriteArrayList 等安全失败(fail-safe) 集合,或者加锁(synchronized)保护迭代代码块。

七、安全失败(fail-safe)机制

1. 什么是 fail-safe?

与 fail-fast 对应,安全失败迭代器在遍历时,不会抛出异常。因为它们操作的是原集合的克隆/快照,修改原集合不会影响遍历中的快照。

2. 典型代表

  • ConcurrentHashMap 的迭代器(弱一致性)
  • CopyOnWriteArrayList 的迭代器(基于快照)
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");

for (String s : list) {  // 底层是 CopyOnWrite 迭代器(安全失败)
    if ("A".equals(s)) {
        list.add("C");   // ✅ 不会抛异常!因为遍历的是快照
    }
}
System.out.println(list);  // [A, B, C](新增元素不影响正在进行的遍历)

八、自定义迭代器实现(面试实战)

如果让你设计一个支持迭代的自定义集合,必须实现 Iterable 接口并重写 iterator() 方法。

public class MyCollection<T> implements Iterable<T> {
    private Object[] elements = new Object[10];
    private int size = 0;

    public void add(T item) {
        elements[size++] = item;
    }

    // 1. 实现 Iterable 接口,返回迭代器
    @Override
    public Iterator<T> iterator() {
        return new MyIterator();
    }

    // 2. 自定义迭代器内部类
    private class MyIterator implements Iterator<T> {
        private int cursor = 0;

        @Override
        public boolean hasNext() {
            return cursor < size;
        }

        @SuppressWarnings("unchecked")
        @Override
        public T next() {
            if (!hasNext()) throw new NoSuchElementException();
            return (T) elements[cursor++];
        }

        // 简化版本,不支持 remove
    }

    public static void main(String[] args) {
        MyCollection<String> mc = new MyCollection<>();
        mc.add("Java");
        mc.add("Iterator");
        for (String s : mc) {  // 增强 for 循环生效!
            System.out.println(s);
        }
    }
}

九、Java 8 新特性:forEachRemaining()

Iterator 接口在 Java 8 中添加了一个默认方法 forEachRemaining(Consumer<? super E> action),可以一次性消费剩余所有元素。

List<String> list = Arrays.asList("A", "B", "C", "D");
Iterator<String> it = list.iterator();

it.next();  // 跳过 A
it.forEachRemaining(System.out::println);  // 输出 B C D(从当前位置到末尾)

十、思考题(检验是否真的懂了)

// 问题 1:下面代码会抛出什么异常?为什么?
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : list) {
    if ("B".equals(s)) {
        list.remove(s);
    }
}
// 问题 2:下面代码中,ArrayList 和 CopyOnWriteArrayList 的表现有什么不同?
// 场景 A:ArrayList
List<Integer> list1 = new ArrayList<>(Arrays.asList(1,2,3));
for(Integer i : list1) { if(i==2) list1.remove(i); }

// 场景 B:CopyOnWriteArrayList
List<Integer> list2 = new CopyOnWriteArrayList<>(Arrays.asList(1,2,3));
for(Integer i : list2) { if(i==2) list2.remove(i); }
// 问题 3:ListIterator 和 Iterator 都能删除元素,为什么 ListIterator 还能添加元素?

答案(选中下方空白区域查看):

  1. 抛出 ConcurrentModificationException。增强 for 底层是迭代器,list.remove() 修改了 modCount,但迭代器内部的 expectedModCount 没变,导致冲突。
  2. 场景 A 抛出 ConcurrentModificationException(fail-fast);场景 B 正常运行,因为 CopyOnWriteArrayList 是 fail-safe 机制,遍历的是快照,修改原集合不影响遍历。
  3. ListIterator 是专门为 List 设计的,因为 List 有索引结构,add() 方法可以精确控制插入位置(通过游标)。而 Set 是无序的,不支持 add() 到指定位置,所以普通的 Iterator 不提供 add(),以保证所有集合的一致性和安全性。

总结(终极速查表)

知识点一句话记忆
Iterator 核心方法hasNext() 问路,next() 走路,remove() 回头清除
Iterable实现这个接口,才能用增强 for 循环
增强 for底层是迭代器,直接 list.remove() 会翻车
ListIterator双向走,还能增删改,但只能用在 List 上
fail-fast(快速失败)ArrayList、HashMap 默认,检测到并发修改立即抛异常
fail-safe(安全失败)CopyOnWriteArrayList、ConcurrentHashMap,遍历快照,安全但不保证实时一致性
forEachRemainingJava 8 提供,一次性消费剩余所有元素

以上就是Java遍历集合时删除元素抛出异常ConcurrentModificationException的排查与解决的详细内容,更多关于Java遍历集合报错ConcurrentModificationException的资料请关注脚本之家其它相关文章!

相关文章

  • Java中Process类的使用与注意事项说明

    Java中Process类的使用与注意事项说明

    这篇文章主要介绍了Java中Process类的使用与注意事项说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Java实用工具库commons-lang3的使用

    Java实用工具库commons-lang3的使用

    Apache Commons Lang 3是一个流行的Java实用工具库,提供了对java.lang包的扩展,包括字符串操作、正则表达式处理、数字操作、日期和时间操作、随机字符串生成和对象操作等功能
    2025-03-03
  • Java中equals和==的区别小结

    Java中equals和==的区别小结

    == 和 equals() 都用于比较两个对象,本文主要介绍了Java中equals和==的区别小结,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2025-06-06
  • Java如何高效使用OpenCV图像处理库

    Java如何高效使用OpenCV图像处理库

    OpenCV是一个开源的计算机视觉库,它提供了一系列丰富的图像处理和计算机视觉算法,包括图像读取、显示、滤波、特征检测、目标跟踪等功能,这篇文章主要给大家介绍了关于Java如何高效使用OpenCV图像处理库的相关资料,需要的朋友可以参考下
    2024-03-03
  • Java四种拷贝方式实战总结(一文扫清所有拷贝问题)

    Java四种拷贝方式实战总结(一文扫清所有拷贝问题)

    在Java中文件拷贝可以通过多种方式实现,不同方式的性能和适用场景有所差异,文中通过代码介绍的非常详细,这篇文章主要介绍了Java四种拷贝方式的相关资料,需要的朋友可以参考下
    2025-07-07
  • Java基础类深度解析(最新整理)

    Java基础类深度解析(最新整理)

    在Java中,基础类(或称为基本类型类)指的是封装了Java基本数据类型的类,这篇文章给大家介绍Java基础类深度解析,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧
    2026-08-08
  • 如何使用Java 8函数式编程优雅处理多层嵌套数据

    如何使用Java 8函数式编程优雅处理多层嵌套数据

    Java8是Java语言历史上的一个重大更新,它带来了许多新的特性和改进,其中函数式编程的引入是其亮点之一,这篇文章主要介绍了如何使用Java 8函数式编程优雅处理多层嵌套数据的相关资料,需要的朋友可以参考下
    2026-01-01
  • SpringBoot项目打包成war包并部署在tomcat上运行的操作步骤

    SpringBoot项目打包成war包并部署在tomcat上运行的操作步骤

    我们开发 SpringBoot 项目有时我们会需要打包成 war 包,放入外置的 Tomcat 中进行运行,或者使用工具idea直接启动,便于开发调试,本文给大家分享SpringBoot项目打包成war包并部署在tomcat上运行的操作步骤,感兴趣的朋友一起看看吧
    2024-03-03
  • 基于Netty实现WebSocket的常用处理器及区别解析

    基于Netty实现WebSocket的常用处理器及区别解析

    文章介绍了Netty中的ChannelInboundHandlerAdapter和SimpleChannelInboundHandler处理器,以及WebSocket的几种常见帧类型,本文给大家介绍的非常详细,感兴趣的朋友一起看看吧
    2025-02-02
  • java实现的xml格式化实现代码

    java实现的xml格式化实现代码

    这篇文章主要介绍了java实现的xml格式化实现代码,需要的朋友可以参考下
    2016-11-11

最新评论