迭代器模式:统一集合遍历接口
迭代器模式,说白了就是给集合类提供一个统一的遍历方式。你想想看,Java 里那么多集合——ArrayList、LinkedList、HashSet,它们的底层结构完全不同。如果没有迭代器,你得为每种集合写一套遍历代码,那得多痛苦?
我个人习惯把迭代器模式理解成「遍历的标准化接口」。它让你不用关心集合内部怎么存数据,只要调用 next() 和 hasNext() 就能挨个访问元素。嗯,这里有个关键点——它不暴露集合的内部表示。你拿到的只是一个迭代器,根本不知道底层是数组还是链表。
为什么需要迭代器模式?
我在项目中遇到过这样一个场景:系统里同时用了 ArrayList 和 TreeSet,业务逻辑需要遍历这两种集合做数据合并。如果没有迭代器,我得写两套遍历逻辑——ArrayList 用 for 循环下标,TreeSet 用增强 for 或者迭代器。代码里到处都是 if-else 判断集合类型,维护起来简直噩梦。
迭代器模式解决了三个核心问题:
- 统一遍历接口:不管什么集合,遍历方式都一样
- 隐藏内部结构:调用方不需要知道集合底层是数组、链表还是树
- 支持多种遍历方式:同一个集合可以有不同的迭代器实现
核心定义:提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
Java 中的 Iterator 接口
Java 标准库早就内置了迭代器模式。你看 java.util.Iterator 接口,就三个核心方法:
public interface Iterator<E> {
boolean hasNext(); // 是否还有下一个元素
E next(); // 返回下一个元素
default void remove() { // 移除当前元素(可选操作)
throw new UnsupportedOperationException();
}
}
所有集合类都实现了 Iterable 接口,返回自己的迭代器。这就是为什么你能用增强 for 循环遍历任何集合——底层其实就是调用了 iterator() 方法。
我记得有一次排查性能问题,发现同事在遍历 ArrayList 时用了迭代器,但 ArrayList 的随机访问用 for 循环更快。迭代器模式虽然统一了接口,但不同集合的性能特性还是要注意的。
自定义双向迭代器
Java 标准迭代器只支持正序遍历。但实际业务中,我们经常需要倒序遍历。比如展示操作日志,最新的日志在最前面。这时候如果只能正序遍历,你得先全部加载到内存再反转,效率很低。
下面我实现一个支持正序和倒序遍历的列表,核心是提供两个迭代器:
import java.util.Iterator;
import java.util.NoSuchElementException;
public class BidirectionalList<T> implements Iterable<T> {
private Object[] elements;
private int size;
public BidirectionalList(int capacity) {
elements = new Object[capacity];
size = 0;
}
public void add(T element) {
if (size >= elements.length) {
throw new ArrayIndexOutOfBoundsException("列表已满");
}
elements[size++] = element;
}
// 正序迭代器
@Override
public Iterator<T> iterator() {
return new ForwardIterator();
}
// 倒序迭代器
public Iterator<T> reverseIterator() {
return new ReverseIterator();
}
// 正序迭代器实现
private class ForwardIterator implements Iterator<T> {
private int currentIndex = 0;
@Override
public boolean hasNext() {
return currentIndex < size;
}
@Override
@SuppressWarnings("unchecked")
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return (T) elements[currentIndex++];
}
}
// 倒序迭代器实现
private class ReverseIterator implements Iterator<T> {
private int currentIndex = size - 1;
@Override
public boolean hasNext() {
return currentIndex >= 0;
}
@Override
@SuppressWarnings("unchecked")
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return (T) elements[currentIndex--];
}
}
}
使用起来非常直观:
BidirectionalList<String> list = new BidirectionalList<>(5);
list.add("A");
list.add("B");
list.add("C");
// 正序遍历
for (String s : list) {
System.out.print(s + " "); // 输出: A B C
}
// 倒序遍历
Iterator<String> reverseIt = list.reverseIterator();
while (reverseIt.hasNext()) {
System.out.print(reverseIt.next() + " "); // 输出: C B A
}
设计要点:迭代器内部持有集合的引用,但只通过接口方法访问数据。这样即使集合内部结构变化,迭代器代码也不需要改。
迭代器模式的核心结构
下面这张图展示了迭代器模式的核心角色和它们之间的关系:
这张图里四个角色:
- Aggregate:聚合接口,定义创建迭代器的方法
- ConcreteAggregate:具体聚合类,比如 ArrayList、LinkedList
- Iterator:迭代器接口,定义 hasNext() 和 next()
- ConcreteIterator:具体迭代器,实现遍历逻辑
实际业务中的迭代器应用
迭代器模式在真实项目中无处不在。除了 Java 集合框架,我还在这些场景中用过:
| 场景 | 说明 |
|---|---|
| 数据库游标 | ResultSet 本质上就是一个迭代器,逐行读取查询结果 |
| 文件行读取 | BufferedReader 的 readLine() 配合循环,就是迭代器模式 |
| 树形结构遍历 | 组织架构树、菜单树,可以用迭代器实现深度优先或广度优先遍历 |
| 分页查询 | 每次取一页数据,用迭代器封装分页逻辑,调用方无感知 |
避坑指南:我曾经在遍历集合时直接调用集合的 remove() 方法,结果抛出了 ConcurrentModificationException。迭代器遍历期间,不要通过集合自身修改结构。正确的做法是用迭代器的 remove() 方法,或者用 CopyOnWriteArrayList 这类线程安全集合。
迭代器模式的优缺点
优点很明显:
- 遍历与集合解耦:遍历逻辑封装在迭代器里,集合只负责存储
- 支持多种遍历:同一个集合可以有不同的迭代器,比如正序、倒序、过滤
- 简化客户端代码:调用方不需要知道集合内部结构
缺点也得提一下:
- 增加类数量:每个集合类都需要对应的迭代器实现
- 遍历过程中修改集合有风险:需要额外处理并发修改问题
我的建议:如果你在设计一个需要被频繁遍历的数据结构,优先考虑实现 Iterable 接口。这样你的类就能直接用在增强 for 循环里,也兼容 Java 的 Stream API。说白了,这是让你的类「更 Java」的做法。
迭代器模式看起来简单,但它是很多复杂设计的基础。比如组合模式里遍历树形结构,就用到了迭代器。再比如 Java 8 的 Stream API,底层也是基于迭代器实现的惰性求值。理解了这个模式,你再看很多框架源码会轻松很多。