迭代器模式有哪些角色?调用流程是什么?
简化版
迭代器模式通常包含迭代器接口、具体迭代器、聚合接口、具体聚合四类角色。调用流程是客户端从聚合对象获取迭代器,再通过迭代器逐个访问元素。
详细版
迭代器模式的角色包括:
- Iterator:迭代器接口,定义
hasNext()、next()等遍历方法。 - ConcreteIterator:具体迭代器,维护遍历位置并实现遍历逻辑。
- Aggregate:聚合接口,定义创建迭代器的方法。
- ConcreteAggregate:具体聚合,保存元素并返回对应迭代器。
客户端一般不直接访问集合内部结构,而是:
- 调用集合的
iterator()。 - 拿到迭代器对象。
- 使用
hasNext()判断。 - 使用
next()取元素。
完整版教学
一、Iterator:迭代器接口
迭代器接口定义统一遍历能力。
interface MyIterator<E> {
boolean hasNext();
E next();
}
这个接口是客户端真正依赖的部分。只要不同集合都能返回这个接口,客户端遍历代码就能统一。
二、ConcreteIterator:具体迭代器
具体迭代器保存遍历状态,比如当前位置。
class ArrayIterator<E> implements MyIterator<E> {
private final E[] elements;
private int cursor = 0;
ArrayIterator(E[] elements) {
this.elements = elements;
}
public boolean hasNext() {
return cursor < elements.length;
}
public E next() {
return elements[cursor++];
}
}
cursor 就是迭代器的游标状态。客户端不用自己维护下标。
三、Aggregate:聚合接口
聚合接口代表可被遍历的集合对象。
interface MyCollection<E> {
MyIterator<E> iterator();
}
这个接口告诉客户端:我可以提供一个迭代器,但不会把内部结构直接给你。
四、ConcreteAggregate:具体聚合
具体聚合负责保存数据,并创建对应迭代器。
class MyArrayList<E> implements MyCollection<E> {
private final E[] elements;
MyArrayList(E[] elements) {
this.elements = elements;
}
public MyIterator<E> iterator() {
return new ArrayIterator<>(elements);
}
}
如果以后底层改成链表,只需要返回链表迭代器,客户端遍历方式不变。
五、完整调用流程
调用过程可以拆成:
- 客户端拿到聚合对象。
- 客户端调用
iterator()创建迭代器。 - 迭代器初始化自己的游标。
- 客户端循环调用
hasNext()。 - 如果还有元素,调用
next()获取元素。 - 迭代器推进游标。
这个流程中,集合负责提供数据来源,迭代器负责控制访问顺序。
六、为什么迭代器要单独成为对象
如果遍历状态放在集合对象里,同一个集合同时被两个地方遍历就会互相干扰。
例如一个循环走到第 3 个元素,另一个循环又从头开始,会覆盖集合内部游标。
把游标放进迭代器对象后,每次调用 iterator() 都可以得到独立遍历状态。多个遍历过程互不影响。
七、常见误区与追问
角色流程从 Aggregate 创建独立 Iterator 开始。客户端请求1个迭代器,迭代器保存 cursor 和集合引用,连续调用 next 直到结束;ConcreteAggregate 仍控制如何访问内部元素。若树做深度优先遍历,ConcreteIterator 还会保存显式栈,而客户端接口不变。
| 检查维度 | 判定依据 |
|---|---|
| Aggregate | 声明创建 Iterator 的能力 |
| ConcreteIterator | 封装游标和具体遍历算法 |
Client -> Aggregate.iterator() -> Iterator -> elements
记忆钩子:集合保存数据,迭代器保存路程,客户端只问还有没有下一站。
- 误区:Iterator 必须复制集合数据。 通常只持有引用和游标;快照型迭代器才会复制或固定视图。
- 追问:一个 Aggregate 能返回多种 Iterator 吗? 可以,例如正序、逆序和层序遍历。
- 误区:ConcreteAggregate 不参与遍历。 它提供底层访问能力并决定迭代器如何读取元素。
- 追问:客户端为什么不直接读内部数组? 隐藏结构能让集合重构而不传播到所有调用方。
- 追问:流程中版本号放在哪里? 集合维护 modCount,迭代器保存创建时的 expectedModCount。
八、加强记忆
迭代器模式的角色可以记成“两边四件套”:聚合接口和具体聚合负责存数据,迭代器接口和具体迭代器负责走数据;客户端只跟迭代器打交道。