从 ConcurrentModificationException 到 fail-safe从 ListIterator 到自定义迭代器一篇带你真正掌握迭代器我们在日常开发中几乎天天都在遍历集合。但你有没有想过为什么在遍历ArrayList时用list.remove()会抛异常但用iterator.remove()却没事为什么ConcurrentHashMap的迭代器不会抛异常Iterator和ListIterator到底有什么区别这些问题的答案都藏在迭代器Iterator的设计哲学里。今天这篇文章我们从接口设计、快速失败fail-fast机制、双向遍历ListIterator、自定义实现到生产级避坑把Iterator彻底讲透。一、先上结论一张表看懂核心角色角色接口/类核心职责关键方法特点可迭代对象IterableT返回迭代器被遍历的对象iterator()增强for循环的“入场券”迭代器单向IteratorE单向遍历并安全删除hasNext()、next()、remove()最基础、最通用迭代器双向ListIteratorE双向遍历支持增/改hasPrevious()、previous()、add()、set()仅限List及其子类拆分迭代器SpliteratorE并行遍历Java 8trySplit()、forEachRemaining()Stream并行流的底层一句话记忆口诀Iterable→ 容器本身能产生迭代器。Iterator→ 单向箭头只能往前走能删。ListIterator→ 双向箭头能前能后能增能改。Spliterator→ 多箭头分块并行。二、Iterator基础为什么不用普通for循环1. 三种遍历方式对比ListStringlistnewArrayList(Arrays.asList(A,B,C));// 方式1普通 for 循环依赖下标for(inti0;ilist.size();i){System.out.println(list.get(i));}// 方式2增强 for 循环语法糖底层也是迭代器for(Strings:list){System.out.println(s);}// 方式3显式迭代器最灵活IteratorStringitlist.iterator();while(it.hasNext()){Stringsit.next();System.out.println(s);}2. 为什么需要迭代器解决了什么问题统一遍历接口无论List、Set、Queue遍历方式完全一致解耦数据结构。安全的删除操作普通for循环删除元素会导致索引错乱增强for循环直接删除会抛异常只有Iterator.remove()才是安全的。隐藏底层实现你不需要关心是数组还是链表只管hasNext()next()。三、核心方法详解hasNext、next、remove1.hasNext()和next()遍历的基本动作IteratorStringitlist.iterator();while(it.hasNext()){// 1. 问还有吗Stringsit.next();// 2. 拿拿到下一个并且游标后移System.out.println(s);}注意如果没有调用hasNext()直接调用next()当没有元素时会抛出NoSuchElementException。2.remove()唯一安全的删除方式这是迭代器最核心的实用价值。ListIntegernumbersnewArrayList(Arrays.asList(1,2,3,4,5));// ✅ 正确使用迭代器删除偶数IteratorIntegeritnumbers.iterator();while(it.hasNext()){Integernumit.next();if(num%20){it.remove();// 安全删除当前元素}}System.out.println(numbers);// [1, 3, 5]3. ⚠️remove()的两条铁律面试常问每次next()后只能调用一次remove()。必须先调用next()再调用remove()不能连续删除也不能在next()之前删除。IteratorIntegeritlist.iterator();while(it.hasNext()){// it.remove(); // ❌ 报错IllegalStateException没有先 nextIntegernumit.next();it.remove();// it.remove(); // ❌ 报错IllegalStateException连续删除两次}四、增强for循环的“甜蜜陷阱”重点语法糖for (String s : list)编译后就是while (it.hasNext()) { String s it.next(); ... }。致命错误在增强for循环中直接调用list.remove()。ListStringlistnewArrayList(Arrays.asList(A,B,C));// ❌ 错误抛出 ConcurrentModificationExceptionfor(Strings: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、LinkedList2. 使用示例双向遍历 添加ListStringlistnewArrayList(Arrays.asList(A,B,D));ListIteratorStringlitlist.listIterator();// 1. 从前往后遍历while(lit.hasNext()){Stringslit.next();if(B.equals(s)){lit.add(C);// 在 B 后面插入 C游标自动跳过新元素}}System.out.println(list);// [A, B, C, D]// 2. 从后往前遍历while(lit.hasPrevious()){Stringslit.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-fastJava 集合ArrayList、HashMap等的迭代器在设计时采用了快速失败机制当迭代器被创建后如果集合内容被非迭代器方式如直接list.remove()修改迭代器会立即抛出ConcurrentModificationException而不是冒着风险继续执行。2. 底层原理源码视角// AbstractList 源码片段publicabstractclassAbstractListE{protectedtransientintmodCount0;// 集合结构修改次数// 迭代器内部privateclassItrimplementsIteratorE{intexpectedModCountmodCount;// 创建迭代器时保存的快照publicEnext(){checkForComodification();// 每次 next 都检查// ...}finalvoidcheckForComodification(){if(modCount!expectedModCount)thrownewConcurrentModificationException();}}}当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的迭代器基于快照CopyOnWriteArrayListStringlistnewCopyOnWriteArrayList();list.add(A);list.add(B);for(Strings:list){// 底层是 CopyOnWrite 迭代器安全失败if(A.equals(s)){list.add(C);// ✅ 不会抛异常因为遍历的是快照}}System.out.println(list);// [A, B, C]新增元素不影响正在进行的遍历八、自定义迭代器实现面试实战如果让你设计一个支持迭代的自定义集合必须实现Iterable接口并重写iterator()方法。publicclassMyCollectionTimplementsIterableT{privateObject[]elementsnewObject[10];privateintsize0;publicvoidadd(Titem){elements[size]item;}// 1. 实现 Iterable 接口返回迭代器OverridepublicIteratorTiterator(){returnnewMyIterator();}// 2. 自定义迭代器内部类privateclassMyIteratorimplementsIteratorT{privateintcursor0;OverridepublicbooleanhasNext(){returncursorsize;}SuppressWarnings(unchecked)OverridepublicTnext(){if(!hasNext())thrownewNoSuchElementException();return(T)elements[cursor];}// 简化版本不支持 remove}publicstaticvoidmain(String[]args){MyCollectionStringmcnewMyCollection();mc.add(Java);mc.add(Iterator);for(Strings:mc){// 增强 for 循环生效System.out.println(s);}}}九、Java 8 新特性forEachRemaining()Iterator接口在 Java 8 中添加了一个默认方法forEachRemaining(Consumer? super E action)可以一次性消费剩余所有元素。ListStringlistArrays.asList(A,B,C,D);IteratorStringitlist.iterator();it.next();// 跳过 Ait.forEachRemaining(System.out::println);// 输出 B C D从当前位置到末尾十、思考题检验是否真的懂了// 问题 1下面代码会抛出什么异常为什么ListStringlistnewArrayList(Arrays.asList(A,B,C));for(Strings:list){if(B.equals(s)){list.remove(s);}}// 问题 2下面代码中ArrayList 和 CopyOnWriteArrayList 的表现有什么不同// 场景 AArrayListListIntegerlist1newArrayList(Arrays.asList(1,2,3));for(Integeri:list1){if(i2)list1.remove(i);}// 场景 BCopyOnWriteArrayListListIntegerlist2newCopyOnWriteArrayList(Arrays.asList(1,2,3));for(Integeri:list2){if(i2)list2.remove(i);}// 问题 3ListIterator 和 Iterator 都能删除元素为什么 ListIterator 还能添加元素答案选中下方空白区域查看抛出ConcurrentModificationException。增强for底层是迭代器list.remove()修改了modCount但迭代器内部的expectedModCount没变导致冲突。场景 A抛出ConcurrentModificationExceptionfail-fast场景 B正常运行因为CopyOnWriteArrayList是 fail-safe 机制遍历的是快照修改原集合不影响遍历。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 提供一次性消费剩余所有元素 互动话题你在实际开发中有没有因为遍历时删除元素导致ConcurrentModificationException的“血泪史”或者用过CopyOnWriteArrayList踩过性能的坑欢迎评论区分享如果觉得有收获别忘了点赞、收藏、转发让更多 Javaer 彻底搞懂迭代器的底层原理我们下篇见发布日期2026-08-30