java集合迭代“失效”实为fail-fast与fail-safe机制差异所致:前者通过modcount校验抛concurrentmodificationexception,后者基于快照或弱一致性视图避免异常;应依场景选型并正确使用remove()、并发集合或removeif/stream。

Java 中集合迭代“失效”并非真正意义上的功能损坏,而是指迭代过程中因结构修改触发 ConcurrentModificationException(如 ArrayList、HashMap 的迭代器),或行为与预期不符(如 ConcurrentHashMap 迭代不反映实时变更)。这种“失效感”源于不同集合对 fail-fast 与 fail-safe 机制的差异化实现。处理关键不是统一行为,而是理解差异、按场景选型、用对方法。
看懂底层差异:modCount 是 fail-fast 的心跳,快照是 fail-safe 的底牌
大多数 java.util 包下的集合(ArrayList、HashMap、HashSet、LinkedList)采用 fail-fast 设计:
- 内部维护一个
modCount计数器,每次 add/remove/resize 等结构性操作就 +1; - 迭代器创建时记录当时的
modCount值为expectedModCount; - 每次调用
next()或hasNext()时检查两者是否一致,不一致立刻抛异常。
而 fail-safe 集合(如 CopyOnWriteArrayList、ConcurrentHashMap)走另一条路:
-
CopyOnWriteArrayList:迭代器基于创建时刻的数组不可变副本工作,后续写操作不影响当前迭代; -
ConcurrentHashMap:迭代器看到的是某个时间点的弱一致性视图,可能不包含最新插入/删除,但绝不会抛ConcurrentModificationException。
安全删除:别用集合自身的 remove(),用迭代器自己的 remove()
这是最常被忽略却最易修复的“失效”场景。在 fail-fast 集合中遍历时,直接调用 list.remove(obj) 或 set.remove(obj) 必然触发异常。
正确做法是只使用迭代器提供的 remove() 方法(它会同步更新 expectedModCount):
Iterator<string> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.startsWith("tmp")) {
it.remove(); // ✅ 安全,迭代器自己协调状态
}
}
</string>
注意:remove() 必须紧跟在 next() 之后调用,且每轮最多调用一次。
多线程环境:别给普通集合加 synchronized,换专用并发集合
试图用 synchronized(list) 包裹迭代逻辑,看似保险,实则治标不治本——锁粒度粗、性能差,且无法彻底避免迭代中其他线程修改引发的异常。
更合理的方案是根据读写比例选择原生并发集合:
- 读多写少(如监听器列表)→
CopyOnWriteArrayList; - 高并发读写(如缓存映射)→
ConcurrentHashMap; - 需要强一致性且能接受阻塞 →
Collections.synchronizedList(new ArrayList())(但迭代仍需手动同步块)。
例如:
List<string> safeList = new CopyOnWriteArrayList();
// 多个线程可同时 add / iterate,互不干扰
safeList.add("item1");
Iterator<string> it = safeList.iterator(); // 迭代不受后续 add 影响
</string></string>
批量过滤或转换:绕过迭代器,用 Stream 或 removeIf()
如果目标只是“删掉满足条件的元素”,硬写 while+iterator 易出错。Java 8+ 提供了更声明式、更安全的替代方案:
-
removeIf(Predicate):直接在集合上操作,内部已做兼容处理(ArrayList 实现中会重置 modCount); -
stream().filter().collect():生成全新集合,完全规避原集合修改问题。
示例:
// 推荐:一行解决,无异常风险
list.removeIf(s -> s == null || s.trim().isEmpty());
// 或函数式风格(适合复杂逻辑)
List<string> filtered = list.stream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
</string>
不复杂但容易忽略:差异不在“能不能用”,而在“为什么这样设计”。理解 modCount 和快照的本质,才能在 ArrayList 的严格校验、ConcurrentHashMap 的宽松视图、CopyOnWriteArrayList 的内存换安全之间,做出清醒选择。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











