# 集合的遍历与迭代机制

作者: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
    }
}
1
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);
}
1
2
3

优点是语法简洁、代码易读,但缺点是不能在循环体内安全修改集合结构,否则也会抛ConcurrentModificationException

示例:

for (String s : list) {
    if ("B".equals(s)) {
        list.remove(s); // 会抛异常
    }
}
1
2
3
4
5

# Stream流遍历

Java 8引入了Stream API,遍历方式更加灵活:

list.stream().forEach(System.out::println);
1

但Stream对底层集合的fail-fast机制依然有效,因此修改集合时同样要注意。


# 三、实战:如何安全遍历并修改集合

遍历集合时的修改操作是最容易踩坑的地方。下面介绍几种常用且安全的处理方式。

# 使用Iterator的remove()安全删除元素

Iteratorremove()方法允许在遍历时安全删除当前元素,不会抛异常。

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    if ("B".equals(s)) {
        iterator.remove();  // 安全删除
    }
}
1
2
3
4
5
6
7

# 遍历集合的副本,修改原集合

遍历一个集合的副本可以避免ConcurrentModificationException,适合需要在遍历中添加元素的场景。

for (String s : new ArrayList<>(list)) {
    if ("B".equals(s)) {
        list.add("D");  // 安全添加
    }
}
1
2
3
4
5

此时遍历的是副本new ArrayList<>(list),对原集合的修改不会影响遍历过程。

# 使用集合提供的批量操作方法

Java集合自带了许多便捷的批量操作,如removeIf,适合删除满足条件的元素。

list.removeIf(s -> "B".equals(s));
1

批量操作本质上在内部安全遍历并修改,使用简单且性能较好。

# 使用线程安全的集合

CopyOnWriteArrayList是Java并发包中的线程安全集合,支持在遍历时修改元素,不会抛异常,但性能开销较大(每次修改都会复制底层数组)。

CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>(list);
for (String s : cowList) {
    if ("B".equals(s)) {
        cowList.add("D");  // 不会抛异常
    }
}
1
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--;  // 移除后索引回退,防止跳过元素
    }
}
1
2
3
4
5
6

一定要配合索引回退操作,否则会跳过元素,产生bug。