# 集合的遍历与迭代机制
作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
在Java开发中,集合的遍历是必不可少的操作。合理掌握集合的遍历方式和迭代机制,能够避免出现ConcurrentModificationException异常,写出更安全、健壮的代码。
# 一、Iterator与fail-fast机制详解
# 什么是Iterator?
Iterator是Java集合框架定义的遍历接口,所有支持遍历的集合都实现了它。它提供了三个核心方法:
boolean hasNext():判断是否还有下一个元素E next():获取下一个元素void remove():从集合中移除当前元素(可选操作)
# fail-fast机制
Java大多数集合的迭代器实现了fail-fast机制。意思是在使用Iterator遍历集合时,如果集合的结构被非法修改(除了使用Iterator.remove()方法),会立即抛出ConcurrentModificationException异常。
示例:
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
if ("B".equals(s)) {
list.remove(s); // 这里会抛 ConcurrentModificationException
}
}
2
3
4
5
6
7
8
9
异常原因是:list.remove(s)直接修改了集合结构,导致Iterator检测到异常。
# 为什么设计fail-fast?
fail-fast能快速暴露出多线程环境或者代码逻辑中不安全的并发修改错误,避免程序在后续执行中出现隐蔽bug,增强代码的健壮性。
# 二、增强for循环和Stream流的遍历方法
# 增强for循环
增强for循环其实是基于Iterator实现的语法糖:
for (String s : list) {
System.out.println(s);
}
2
3
优点是语法简洁、代码易读,但缺点是不能在循环体内安全修改集合结构,否则也会抛ConcurrentModificationException。
示例:
for (String s : list) {
if ("B".equals(s)) {
list.remove(s); // 会抛异常
}
}
2
3
4
5
# Stream流遍历
Java 8引入了Stream API,遍历方式更加灵活:
list.stream().forEach(System.out::println);
但Stream对底层集合的fail-fast机制依然有效,因此修改集合时同样要注意。
# 三、实战:如何安全遍历并修改集合
遍历集合时的修改操作是最容易踩坑的地方。下面介绍几种常用且安全的处理方式。
# 使用Iterator的remove()安全删除元素
Iterator的remove()方法允许在遍历时安全删除当前元素,不会抛异常。
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
if ("B".equals(s)) {
iterator.remove(); // 安全删除
}
}
2
3
4
5
6
7
# 遍历集合的副本,修改原集合
遍历一个集合的副本可以避免ConcurrentModificationException,适合需要在遍历中添加元素的场景。
for (String s : new ArrayList<>(list)) {
if ("B".equals(s)) {
list.add("D"); // 安全添加
}
}
2
3
4
5
此时遍历的是副本new ArrayList<>(list),对原集合的修改不会影响遍历过程。
# 使用集合提供的批量操作方法
Java集合自带了许多便捷的批量操作,如removeIf,适合删除满足条件的元素。
list.removeIf(s -> "B".equals(s));
批量操作本质上在内部安全遍历并修改,使用简单且性能较好。
# 使用线程安全的集合
CopyOnWriteArrayList是Java并发包中的线程安全集合,支持在遍历时修改元素,不会抛异常,但性能开销较大(每次修改都会复制底层数组)。
CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>(list);
for (String s : cowList) {
if ("B".equals(s)) {
cowList.add("D"); // 不会抛异常
}
}
2
3
4
5
6
# 普通for循环遍历修改——谨慎使用
使用传统for (int i=0; i<list.size(); i++)循环修改集合时,不会抛异常,但可能会导致逻辑问题:
for (int i = 0; i < list.size(); i++) {
if ("B".equals(list.get(i))) {
list.remove(i);
i--; // 移除后索引回退,防止跳过元素
}
}
2
3
4
5
6
一定要配合索引回退操作,否则会跳过元素,产生bug。