16、迭代器模式(Iterator):迭代器接口、具体迭代器、聚合接口、具体聚合,以及Java集合框架中的迭代器

迭代器模式,说白了就是「把遍历这件事封装起来」。你想想看,我们平时写代码,经常要遍历一个集合。数组用for循环,List用for-each,Map用entrySet……每种数据结构遍历方式都不一样。这其实挺烦的。

我记得刚入行那会儿,写了一个自定义的数据结构,底层是个链表。结果每个调用方都要自己写while循环去遍历节点。后来有个同事看了一眼代码,说:「你为什么不给个迭代器?」嗯,从那以后,我就再也没手写过遍历逻辑了。

什么是迭代器模式?

迭代器模式的核心思想很简单:把「访问集合元素」的行为和「集合本身的存储结构」解耦。你不需要知道底层是数组、链表还是树,你只需要调用 next() 和 hasNext() 就行了。

我个人习惯把迭代器模式拆成四个角色:

  • 迭代器接口(Iterator):定义 hasNext()、next() 等遍历方法
  • 具体迭代器(ConcreteIterator):实现遍历逻辑,维护当前遍历位置
  • 聚合接口(Aggregate):定义创建迭代器的方法,比如 createIterator()
  • 具体聚合(ConcreteAggregate):实现聚合接口,返回对应的具体迭代器

核心要点:迭代器模式让客户端代码与集合的内部结构无关。你换一种底层实现,客户端代码一行都不用改。

一个简单的例子:书架与迭代器

我在项目中遇到过这样一个场景:一个图书管理系统,书架底层用的是数组,但后来要改成ArrayList。如果所有遍历代码都散落在各个业务类里,改起来就痛苦了。用迭代器模式,只需要改迭代器内部实现就行。

先看迭代器接口:

// 迭代器接口
public interface Iterator<T> {
    boolean hasNext();
    T next();
}

再看聚合接口:

// 聚合接口
public interface Aggregate<T> {
    Iterator<T> createIterator();
}

具体聚合——书架:

public class BookShelf implements Aggregate<Book> {
    private Book[] books;
    private int index = 0;

    public BookShelf(int maxSize) {
        this.books = new Book[maxSize];
    }

    public void addBook(Book book) {
        this.books[index] = book;
        index++;
    }

    public Book getBookAt(int index) {
        return books[index];
    }

    public int getLength() {
        return index;
    }

    @Override
    public Iterator<Book> createIterator() {
        return new BookShelfIterator(this);
    }
}

具体迭代器:

public class BookShelfIterator implements Iterator<Book> {
    private BookShelf bookShelf;
    private int position = 0;

    public BookShelfIterator(BookShelf bookShelf) {
        this.bookShelf = bookShelf;
    }

    @Override
    public boolean hasNext() {
        return position < bookShelf.getLength();
    }

    @Override
    public Book next() {
        Book book = bookShelf.getBookAt(position);
        position++;
        return book;
    }
}

客户端使用:

BookShelf bookShelf = new BookShelf(5);
bookShelf.addBook(new Book("设计模式"));
bookShelf.addBook(new Book("重构"));

Iterator<Book> it = bookShelf.createIterator();
while (it.hasNext()) {
    Book book = it.next();
    System.out.println(book.getName());
}

你看,客户端完全不知道底层是数组。如果哪天我把 BookShelf 改成用 ArrayList 存储,只需要改 BookShelf 内部和 BookShelfIterator 的实现,客户端代码纹丝不动。

Java集合框架中的迭代器

其实Java的集合框架早就把迭代器模式玩得炉火纯青了。你每天都在用,只是可能没意识到。

Java的 java.util.Iterator 接口就是标准的迭代器接口:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    default void remove() { throw new UnsupportedOperationException(); }
    default void forEachRemaining(Consumer<? super E> action) { ... }
}

而 java.util.Collection 接口继承了 Iterable 接口,Iterable 要求返回一个 Iterator:

public interface Iterable<T> {
    Iterator<T> iterator();
    default void forEach(Consumer<? super T> action) { ... }
    default Spliterator<T> spliterator() { ... }
}

所以你看,ArrayList、HashSet、LinkedList 这些类,都实现了 iterator() 方法,返回各自的具体迭代器。

小提示:Java的 for-each 循环本质上就是语法糖,底层还是调用了 iterator() 和 hasNext()、next()。你写 for (Book b : bookShelf) 的时候,编译器会帮你生成迭代器调用的代码。

迭代器模式的结构图

下面我用一张SVG图来展示迭代器模式的整体结构。这张图我画了很多次,每次给团队做分享都会用到。

迭代器模式结构图 <<接口>> Iterator + hasNext(): boolean + next(): T ConcreteIterator - position: int + hasNext(): boolean + next(): T <<接口>> Aggregate + createIterator(): Iterator ConcreteAggregate - data: T[] + createIterator(): Iterator 实现 实现 创建 Client(客户端) 依赖 依赖

迭代器模式的优缺点

我在实际项目中用迭代器模式比较多,说说我的感受。

优点 缺点
遍历逻辑与集合结构解耦,客户端代码更简洁 对于简单的集合(比如数组),引入迭代器反而增加了复杂度
支持多种遍历方式(正序、倒序、过滤遍历等) 如果集合结构频繁变化,迭代器需要同步更新
统一了不同集合的遍历接口 多线程环境下,迭代器可能抛出 ConcurrentModificationException

避坑指南:我曾经在一个多线程项目中,一边遍历一边修改集合,结果抛出了 ConcurrentModificationException。后来改用 CopyOnWriteArrayList 或者显式加锁才解决。记住:迭代器是「快照」模式还是「弱一致性」模式,取决于具体实现,不要想当然。

C++ 中的迭代器

C++ 的迭代器跟 Java 不太一样。C++ 标准库的迭代器更像是一种「指针的泛化」。你想想看,C++ 的 vector::iterator 本质上就是一个包装过的指针,支持 ++、--、* 等操作符。

#include <iostream>
#include <vector>

int main() {
    std::vector<std::string> books = {"设计模式", "重构", "代码整洁之道"};
    
    // 使用迭代器遍历
    for (auto it = books.begin(); it != books.end(); ++it) {
        std::cout << *it << std::endl;
    }
    
    // C++11 范围for循环(底层也是迭代器)
    for (const auto& book : books) {
        std::cout << book << std::endl;
    }
    
    return 0;
}

C++ 的迭代器分为五种类型:输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器。每种迭代器支持的操作不同。比如 vector 的迭代器是随机访问迭代器,支持 it + 5 这种操作;而 list 的迭代器是双向迭代器,只能 ++ 和 --。

小提示:如果你在 C++ 中自定义一个容器,想让它支持范围 for 循环,只需要实现 begin() 和 end() 方法,返回对应的迭代器对象即可。这个套路跟 Java 的 Iterable 接口如出一辙。

什么时候用迭代器模式?

我个人习惯在以下场景使用迭代器模式:

  • 你的集合内部结构可能变化,但不想影响客户端代码
  • 你需要提供多种遍历方式(比如正序、倒序、按条件过滤)
  • 你想统一不同集合的遍历接口,让客户端代码更通用
  • 你不想暴露集合的内部结构(比如不想让客户端直接操作数组下标)

反过来,如果你的集合很简单,比如就是一个固定大小的数组,而且永远不会变,那直接用 for 循环反而更直接。设计模式不是银弹,别为了用模式而用模式。

好了,迭代器模式就聊到这儿。这个模式看起来简单,但它是很多高级模式(比如组合模式、访问者模式)的基础。理解透了,后面学起来会轻松很多。


公众号:蓝海资料掘金营,微信deep3321