迭代器模式:统一遍历、内部迭代与外部迭代、实战:自定义集合

迭代器模式,说白了就是给集合类提供一个统一的“翻书”方式。你想想看,数组用下标遍历,List用get(i),Map用keySet,Set又没顺序——每次换个集合类型就得重写遍历逻辑,这谁受得了?

我在项目中遇到过好几次这样的场景:业务方换了个底层存储结构,结果所有遍历代码都得跟着改。嗯,那时候我就意识到,迭代器模式不是锦上添花,而是刚需。

什么是迭代器模式?

迭代器模式的核心思想很简单:把遍历行为从集合类中抽离出来。集合只负责存数据,迭代器负责怎么取数据。这样两者各司其职,互不干扰。

用一句话概括:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部表示

核心角色:

  • Iterator(迭代器接口):定义 hasNext()、next() 等遍历方法
  • ConcreteIterator(具体迭代器):实现具体的遍历逻辑
  • Aggregate(聚合接口):定义创建迭代器的方法
  • ConcreteAggregate(具体聚合):实现集合存储,并返回对应的迭代器
Iterator(迭代器接口) +hasNext(): boolean +next(): Object Aggregate(聚合接口) +createIterator(): Iterator ConcreteIterator 维护当前遍历位置 ConcreteAggregate 存储数据,返回迭代器 创建 迭代器模式核心结构 集合只负责存储,迭代器负责遍历

内部迭代 vs 外部迭代

迭代器模式有两种实现风格,我习惯把它们比作“你翻书”和“书自己翻”。

外部迭代(External Iterator)

调用方主动控制遍历过程。你调用 hasNext() 判断,再调 next() 取数据。说白了,主动权在你手里。

// 外部迭代——调用方控制循环
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    System.out.println(item);
}

优点:灵活,可以在遍历过程中做复杂判断,甚至暂停遍历。

缺点:代码啰嗦,每次都得写 while 循环。

内部迭代(Internal Iterator)

你把一个“回调函数”丢给迭代器,让它自己跑完。Java 8 的 forEach 就是典型例子。

// 内部迭代——迭代器自己控制
list.forEach(item -> System.out.println(item));

优点:代码简洁,关注点只在“做什么”,不用管“怎么遍历”。

缺点:不够灵活,想中途跳出?得用异常或者流式操作的短路方法。

我的建议:业务逻辑简单、不需要中途中断的场景,用内部迭代。需要精细控制遍历节奏的,用外部迭代。我在做报表导出时,经常用外部迭代来分批处理大数据量集合。

Java 中的迭代器实现

Java 集合框架早就内置了迭代器模式。你随便拿个 ArrayList 或 HashSet,它们都实现了 Iterable 接口,返回一个 Iterator 对象。

// Java 内置迭代器示例
List<String> names = Arrays.asList("张三", "李四", "王五");
Iterator<String> it = names.iterator();

while (it.hasNext()) {
    String name = it.next();
    if ("李四".equals(name)) {
        it.remove();  // 安全删除
    }
}
System.out.println(names); // 输出:[张三, 王五]

注意这里的 remove() 方法。直接用集合的 remove() 会抛 ConcurrentModificationException,但迭代器的 remove() 是安全的。我曾经踩过这个坑,遍历时直接调 list.remove(),结果程序运行时好时坏,排查了半天才发现是并发修改问题。

避坑指南:遍历集合时如果要删除元素,一定要用迭代器的 remove() 方法,或者用 Java 8 的 removeIf()。直接调用集合的 remove() 会破坏迭代器的状态,导致异常。

实战:自定义集合与迭代器

光说不练假把式。我们来手写一个简单的自定义集合——一个固定大小的“书架”,然后给它配上迭代器。

第一步:定义聚合接口

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

第二步:实现具体集合

// 书架——一个固定大小的集合
public class BookShelf<T> implements Aggregate<T> {
    private T[] books;
    private int size = 0;

    @SuppressWarnings("unchecked")
    public BookShelf(int maxSize) {
        this.books = (T[]) new Object[maxSize];
    }

    public void add(T book) {
        if (size < books.length) {
            books[size++] = book;
        } else {
            throw new RuntimeException("书架已满");
        }
    }

    public int getSize() {
        return size;
    }

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

第三步:实现具体迭代器

// 书架迭代器
public class BookShelfIterator<T> implements Iterator<T> {
    private BookShelf<T> shelf;
    private int index = 0;

    public BookShelfIterator(BookShelf<T> shelf) {
        this.shelf = shelf;
    }

    @Override
    public boolean hasNext() {
        return index < shelf.getSize();
    }

    @Override
    public T next() {
        if (!hasNext()) {
            throw new NoSuchElementException("没有更多元素了");
        }
        return shelf.get(index++);
    }
}

第四步:使用自定义集合

// 客户端代码
BookShelf<String> shelf = new BookShelf<>(5);
shelf.add("设计模式");
shelf.add("Java核心技术");
shelf.add("重构");

Iterator<String> it = shelf.createIterator();
while (it.hasNext()) {
    String book = it.next();
    System.out.println("📖 " + book);
}

输出结果:

📖 设计模式
📖 Java核心技术
📖 重构

关键点总结:

  • 集合类(BookShelf)只负责存储,不关心遍历逻辑
  • 迭代器类(BookShelfIterator)只负责遍历,不关心存储细节
  • 两者通过接口解耦,可以独立变化
  • 客户端只依赖 Iterator 接口,不依赖具体集合实现

迭代器模式的应用场景

场景 说明 示例
统一遍历接口 不同集合类型使用相同方式遍历 ArrayList、HashSet 都返回 Iterator
隐藏内部结构 不暴露集合的底层数据结构 树形结构、图结构的遍历
支持多种遍历方式 正序、倒序、过滤等不同迭代器 TreeSet 的升序/降序迭代器
延迟遍历 按需生成元素,节省内存 数据库游标、大文件逐行读取

我的经验:迭代器模式特别适合“数据源不确定”的场景。比如你写一个报表工具,数据可能来自数据库、缓存、或者内存计算。给每个数据源配一个迭代器,上层代码完全不用改。我曾经用这个思路重构过一个报表模块,代码量减少了40%,扩展性却大大提升。

迭代器模式的优缺点

优点:

  • 遍历逻辑与集合解耦,符合单一职责原则
  • 新增集合类型不影响已有遍历代码
  • 支持同时多个迭代器遍历同一个集合
  • 可以延迟遍历,节省内存

缺点:

  • 对于简单集合,引入迭代器会增加代码复杂度
  • 迭代器本身是状态对象,多线程环境下需要额外同步
  • 某些场景下性能不如直接下标访问(比如数组)

嗯,总的来说,迭代器模式是那种“用的时候不觉得,没了它才知道痛”的设计。你想想看,如果没有迭代器,Java 集合框架得乱成什么样?每个集合都得暴露自己的内部结构,那封装性就全毁了。

我个人习惯在写工具类库时,优先考虑迭代器模式。它让代码更优雅,也让调用方更省心。下次你写一个自定义集合时,不妨试试给它配个迭代器——你会发现,遍历这件事,真的可以很统一。


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