跳到主要内容

迭代器模式-Iterator Pattern

序言​

想一想我们在写 Java 代码时最常用的操作是什么?遍历集合。从 ArrayList 到 HashSet,再到我们自己定义的复杂数据结构,几乎都需要遍历其中的元素。

如果遍历方式因集合内部结构而异——数组用下标、链表用指针、树用递归——那么客户端就必须知道每一种集合的内部结构才能遍历它。这既破坏了封装,也让代码变得僵化:集合内部结构一改,所有遍历代码都要跟着改。

迭代器模式(Iterator Pattern)正是为了解决这个问题而生:把"遍历"这个行为从集合中分离出来,封装成独立的迭代器对象,客户端只需要调用 hasNext() / next(),完全不关心集合底层是数组、链表还是树。

这种思想在现代编程中已经无处不在:Java 的 for-each 循环、Python 的 for...in、JavaScript 的 for...of,背后都是迭代器模式。

定义​

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

这里有两个关键点:

  1. 顺序访问:迭代器提供统一的顺序访问接口,客户端不必关心集合内部如何存储;
  2. 不暴露内部表示:集合的内部结构对客户端完全隐藏,迭代器是客户端访问集合的"唯一通道"。

结构​

让我们从一个问题开始​

假设我们在图书馆管理系统中有两类藏书:一类存在书架上(数组),一类存在仓库里(链表)。如果不使用迭代器模式,遍历书架和遍历仓库需要两套完全不同的代码:

// 书架:数组结构
Book[] shelf = library.getShelfBooks();
for (int i = 0; i < shelf.length; i++) {
System.out.println(shelf[i].getName());
}

// 仓库:链表结构
LinkedList<Book> warehouse = library.getWarehouseBooks();
for (Node<Book> node = warehouse.head; node != null; node = node.next) {
System.out.println(node.data.getName());
}

客户端代码必须知道"书架是数组、仓库是链表"这些内部细节,一旦存储结构变化(比如书架改成 List),所有遍历代码都要修改。更糟糕的是,如果图书馆还有第三种存储方式(比如按分类树存储),遍历代码会越来越多、越来越乱。

迭代器模式的结构​

迭代器模式把"聚合对象"和"遍历行为"分离:

各个角色的职责如下:

  • Iterator(抽象迭代器):声明遍历集合所需的接口,通常包含 hasNext()(是否还有下一个元素)和 next()(返回下一个元素并移动游标)。
  • ConcreteIterator(具体迭代器):实现迭代器接口,记录当前遍历位置。通常以内部类的形式存在于聚合对象中,因为只有它知道聚合对象的内部结构,对应图中的 BookListIterator。
  • Aggregate(抽象聚合):声明创建迭代器对象的接口 iterator()。
  • ConcreteAggregate(具体聚合):实现聚合接口,返回一个具体的迭代器实例,对应图中的 BookList。
  • 客户端只依赖 Iterator 接口遍历,不接触集合内部。

代码实现​

我们来实现一个简单的书架(BookList)及其迭代器。

定义迭代器接口​

public interface Iterator {
boolean hasNext();

Book next();
}

定义聚合接口和具体聚合​

public interface Aggregate {
Iterator iterator();
}
import java.util.ArrayList;
import java.util.List;

public class BookList implements Aggregate {

private final List<Book> books = new ArrayList<>();

public void add(Book book) {
books.add(book);
}

public Book getBook(int index) {
return books.get(index);
}

public int size() {
return books.size();
}

@Override
public Iterator iterator() {
// 返回一个迭代器实例
return new BookListIterator(this);
}
}

定义具体迭代器​

迭代器内部持有一个"当前游标"(index),通过游标控制遍历进度:

public class BookListIterator implements Iterator {

private final BookList bookList;
private int index;

public BookListIterator(BookList bookList) {
this.bookList = bookList;
this.index = 0;
}

@Override
public boolean hasNext() {
return index < bookList.size();
}

@Override
public Book next() {
return bookList.getBook(index++);
}
}

客户端使用​

public class Main {

public static void main(String[] args) {
BookList bookList = new BookList();
bookList.add(new Book("设计模式:可复用面向对象软件的基础"));
bookList.add(new Book("深入理解 Java 虚拟机"));
bookList.add(new Book("Java 并发编程实战"));

// 客户端只依赖 Iterator 接口,完全不知道 BookList 的内部结构
Iterator iterator = bookList.iterator();
while (iterator.hasNext()) {
Book book = iterator.next();
System.out.println("书名:" + book.getName());
}
}
}

输出结果:

书名:设计模式:可复用面向对象软件的基础
书名:深入理解 Java 虚拟机
书名:Java 并发编程实战

核心收益:客户端与集合内部结构解耦。将来 BookList 的存储结构从 ArrayList 换成 LinkedList,甚至换成树形结构,只要 iterator() 返回的迭代器接口不变,客户端代码一行都不用改。

在开源框架中的应用​

Java 集合框架:java.util.Iterator​

Java 集合框架是迭代器模式最成功的应用。java.util.Iterator 是所有集合共用的遍历接口:

public interface Iterator<E> {
boolean hasNext();

E next();

default void remove() { ... }

default void forEachRemaining(Consumer<? super E> action) { ... }
}

以 ArrayList 为例,它的迭代器是内部类 Itr,直接访问 ArrayList 内部的 elementData 数组:

// java.util.ArrayList 源码(简化)
public class ArrayList<E> extends AbstractList<E> {

transient Object[] elementData;
private int size;

public Iterator<E> iterator() {
return new Itr();
}

private class Itr implements Iterator<E> {
int cursor; // 下一个要返回的元素的索引
int lastRet = -1; // 上一次返回的元素索引

public boolean hasNext() {
return cursor != size;
}

@SuppressWarnings("unchecked")
public E next() {
...
Object[] elementData = ArrayList.this.elementData;
...
return (E) elementData[lastRet = i];
}
}
}

关键设计:

  1. Itr 是 ArrayList 的内部类,可以无障碍访问 elementData、size 等私有字段——这正是迭代器模式"不暴露内部表示"的实现方式;
  2. 客户端通过 Iterator 接口遍历 ArrayList、LinkedList、HashSet…… 完全一致;
  3. Java 的增强 for-each 循环底层就是编译器生成的迭代器调用:
for (Book book : bookList) {
System.out.println(book.getName());
}

编译后等价于:

for (Iterator<Book> it = bookList.iterator(); it.hasNext(); ) {
Book book = it.next();
System.out.println(book.getName());
}

此外,java.util.ListIterator 在 Iterator 基础上增加了双向遍历(previous())、插入(add)、修改(set)能力,用于 LinkedList 等需要双向操作的数据结构。

适用场景​

  • 当集合内部结构复杂(数组、链表、树、图)且不希望暴露给客户端时,可以使用迭代器模式,让客户端通过统一接口遍历。
  • 当需要为同一个聚合对象提供多种遍历方式(正序、倒序、按条件过滤)时,可以为每种方式提供一个迭代器。
  • 当遍历代码需要在多个地方复用,或者遍历逻辑需要在运行时动态变化时,迭代器模式可以把遍历行为独立出来。
  • 当希望客户端代码与具体集合类解耦,方便将来替换存储实现时,迭代器是理想的选择。

与其他设计模式的关系​

  • 迭代器模式与**组合模式(Composite)**经常结合使用:组合模式中的树形结构(如文件目录树)需要遍历时,迭代器可以隐藏树的递归遍历细节。
  • 迭代器的创建通常由聚合对象的工厂方法完成,这与**工厂方法模式(Factory Method)**天然契合——iterator() 方法就是一个工厂方法。
  • 在迭代过程中对元素执行统一操作,可以配合访问者模式(Visitor);而用**观察者模式(Observer)**可以在迭代过程中监听元素变化。
  • 需要"快照式"遍历(迭代开始时固定集合内容)时,迭代器内部可以借用**备忘录模式(Memento)**保存遍历状态。

总结​

迭代器模式把"遍历聚合对象"这一行为从聚合对象中抽取出来,封装成独立的迭代器对象:

  • 客户端只依赖 Iterator 接口,与集合的具体内部结构解耦;
  • 集合的存储结构可以自由变化,只要迭代器接口不变,客户端代码就无需修改;
  • 支持为同一聚合提供多种遍历方式,遍历逻辑可以独立演化。

迭代器模式的缺点是:对于简单集合(如数组),直接使用下标遍历可能更直接高效;引入迭代器会增加一个抽象层。但在大型、复杂的对象结构中,迭代器带来的解耦和统一性收益远大于这些成本——这也是为什么 Java 集合框架、各种语言的内置遍历语法都采用这一模式的原因。