Java 23 种设计模式:从踩坑到精通 | 迭代器模式 —— 遍历集合,为什么不直接暴露内部结构?

摘要:当需要遍历一个聚合对象(如集合、列表、树),但又不想暴露其内部表示时,直接在聚合类上添加遍历方法会导致职责过重、耦合增加。迭代器模式通过将遍历行为封装为独立的迭代器对象,提供一种统一的方式来顺序访问聚合中的每个元素,而无需关心底层是数组、链表还是树。本文从自定义集合的遍历需求出发,完整讲解迭代器模式的原理、UML、代码实现、与 for-each 语法糖的关系,并结合 JDK 集合框架、数据库游标、MyBatis Cursor 等应用,帮你理解“遍历与数据分离”的设计思想。

🗺️ 本文阅读地图(3 分钟速览)

  • 为什么直接暴露 get(index) 是危险的?
  • 迭代器模式四大角色拆解
  • 手写一个支持正向/反向遍历的自定义集合
  • for-each 语法糖底层原理
  • JDK Iterator / 数据库游标 / MyBatis Cursor 如何体现
  • 面试必问:IteratorIterable 有什么区别?

📖 《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 上一篇:解释器模式 | 当前:迭代器模式 | 下一篇:中介者模式
🔗 返回系列总目录



1. 从“遍历一个自定义列表”的纠结说起

假设你实现了一个自定义的列表类 MyList,内部使用数组存储数据。现在客户端需要遍历列表中的所有元素。最简单的方法是在 MyList 中直接暴露一个 get(index) 方法,客户端通过索引循环:

for (int i = 0; i < myList.size(); i++) {
    System.out.println(myList.get(i));
}

但这要求客户端知道 MyList 是数组结构。如果将来底层改为链表,客户端代码全部需要重写。如果要求客户端不能直接访问索引,只能顺序遍历,又该怎么做?

更复杂的是,如果要求同时支持正向和反向遍历,或者支持遍历时删除元素,直接在聚合类中添加这些功能会让 MyList 膨胀成一团乱麻。

迭代器模式(Iterator Pattern)正是为此而生:它提供一个对象来顺序访问聚合对象中的元素,而不暴露聚合对象的内部表示。把“如何遍历”从聚合类中分离出来,形成一个独立的迭代器对象。

1.1 你的场景该不该用迭代器?

判断标准 是 → 用迭代器 否 → 用其他方式
需要隐藏集合的内部结构,不暴露给客户端
需要支持多种遍历方式(正向、反向、条件过滤)
需要统一不同数据结构的遍历接口
结构简单,只需一种遍历方式 直接用 for-each 即可

2. 模式定义与 UML 结构

迭代器模式 提供一种方法顺序访问一个聚合对象中的各个元素,而又无需暴露该对象的内部表示。它属于 行为型设计模式

迭代器模式

图文解析(配合上述 UML 图)

迭代器模式的核心角色:

  • 抽象迭代器(Iterator:定义访问和遍历元素的接口,包含 hasNext()next()remove() 等方法。
  • 具体迭代器(ConcreteIterator:实现迭代器接口,跟踪遍历的当前位置(如 cursor 索引),知道如何遍历聚合中的元素。
  • 抽象聚合(Aggregate:定义创建迭代器对象的接口,通常只有 createIterator() 一个方法。
  • 具体聚合(ConcreteAggregate:实现聚合接口,返回一个具体的迭代器实例。

核心机制:聚合负责“存”,迭代器负责“取”。客户端只与迭代器交互,完全不知道底层是数组、链表还是树。


3. 代码实现:自定义列表的迭代器

3.1 抽象迭代器

public interface Iterator<T> {
    boolean hasNext();
    T next();
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

💬 白话:迭代器必须能回答“还有下一个吗?”和“把下一个给我”。

3.2 抽象聚合

public interface IterableCollection<T> {
    Iterator<T> createIterator();
}

💬 白话:所有集合都必须能“创建一个迭代器出来”。

3.3 具体聚合:数组列表

public class ArrayBasedList<T> implements IterableCollection<T> {
    private Object[] elements;
    private int size;
    private static final int INIT_CAPACITY = 16;

    public ArrayBasedList() {
        elements = new Object[INIT_CAPACITY];
        size = 0;
    }

    public void add(T item) {
        if (size == elements.length) {
            elements = Arrays.copyOf(elements, size * 2);
        }
        elements[size++] = item;
    }

    public int size() { return size; }

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

    // 具体迭代器作为内部类,可以访问外部聚合的私有字段
    private class ArrayBasedIterator implements Iterator<T> {
        private int cursor = 0;

        @Override
        public boolean hasNext() {
            return cursor < size;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T next() {
            if (!hasNext()) throw new NoSuchElementException();
            return (T) elements[cursor++];
        }
    }
}

💬 白话:迭代器是聚合的“内部员工”,可以直接访问聚合的私有数组,但对外只暴露 hasNext()next()

3.4 客户端调用

ArrayBasedList<String> names = new ArrayBasedList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");

Iterator<String> it = names.createIterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

客户端只需要通过迭代器遍历,完全不知道底层是数组。如果将来改为链表实现,只需修改 ArrayBasedList 内部,客户端代码无需任何改动。


4. 扩展:支持反向遍历的内部类迭代器

// 在 ArrayBasedList 中新增方法
public Iterator<T> createReverseIterator() {
    return new ReverseIterator();
}

private class ReverseIterator implements Iterator<T> {
    private int cursor = size - 1;

    @Override
    public boolean hasNext() {
        return cursor >= 0;
    }

    @Override
    @SuppressWarnings("unchecked")
    public T next() {
        if (!hasNext()) throw new NoSuchElementException();
        return (T) elements[cursor--];
    }
}

💬 白话:同一个聚合可以提供多种迭代器——正向遍历、反向遍历、条件过滤遍历……聚合只负责创建迭代器,遍历逻辑完全由迭代器控制。

客户端调用:

Iterator<String> reverseIt = names.createReverseIterator();
while (reverseIt.hasNext()) {
    System.out.println(reverseIt.next());  // Charlie → Bob → Alice
}

5. 迭代器模式 vs for-each 语法糖

Java 5 引入的 for-each 循环实际就是迭代器模式的语法糖。任何实现了 Iterable 接口的类都可以使用 for-each。编译器会将 for (T item : collection) 编译为迭代器的 while 循环。

// 语法糖
for (String s : names) {
    System.out.println(s);
}

// 编译后等效代码
Iterator<String> it = names.iterator();
while (it.hasNext()) {
    String s = it.next();
    System.out.println(s);
}

💬 白话:你每天都在用迭代器,只是 Java 帮你偷偷写好了 while 循环。


6. 优缺点一览

优点 缺点
封装性:不暴露聚合对象的内部结构 增加类数量:每个聚合类通常对应一个迭代器类
多遍历支持:同时可以存在多个迭代器,互不干扰 简单集合可能过于复杂:引入额外的抽象层
统一接口:遍历算法统一,客户端无须关注数据结构差异 迭代器的 remove() 需要小心:并发修改时可能抛异常
符合单一职责:聚合类只负责存储,迭代器负责遍历

7. 框架与实践中的应用

7.1 JDK 集合框架

java.util.Iteratorjava.lang.Iterable 是迭代器模式最经典的实现。ArrayListLinkedListHashSet 等所有集合类都通过 iterator() 方法返回对应的迭代器。

List<String> list = new ArrayList<>();
list.add("A"); list.add("B");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

7.2 数据库结果集游标

JDBC 的 ResultSet 本质上就是一个迭代器。它提供 next() 方法逐行遍历查询结果,getString()getInt() 等方法获取当前行字段值,完全屏蔽了数据库内部的数据结构。

7.3 MyBatis 的 Cursor

MyBatis 3.5 引入的 Cursor 接口继承了 Iterable,允许对大量查询结果进行流式迭代处理,避免一次性加载所有数据到内存。

try (Cursor<User> cursor = sqlSession.selectCursor("selectUsers")) {
    for (User user : cursor) {
        process(user);
    }
}

8. 面试必问 + 面试官追问连环炮

基础必问

  • 迭代器模式有哪些角色? → 抽象迭代器、具体迭代器、抽象聚合、具体聚合。
  • Java 的 IteratorIterable 有什么区别?Iterator 是迭代器本身,Iterable 是能够返回迭代器的聚合对象。
  • 为什么需要迭代器模式? → 分离遍历与存储,提供统一遍历接口,支持多种遍历方式。

面试官追问

  • “如何在 for-each 中删除元素?”
    👉 不能直接调用 list.remove(),会抛 ConcurrentModificationException,必须使用 Iterator.remove()
  • “迭代器模式如何支持多种遍历方式?”
    👉 同一个聚合类提供多个 createXxxIterator() 方法,分别返回正向、反向、条件过滤等不同的迭代器实现。
  • ConcurrentModificationException 是怎么检测出来的?”
    👉 集合内部维护一个 modCount,迭代器创建时记录 expectedModCount,每次操作时比较两者是否一致。

🎉 恭喜:如果你能立刻说出 IteratorIterable 的区别,并理解 for-each 语法糖的底层原理,你已经掌握了 Java 中最基础也最重要的设计模式之一。


9. 六大设计原则在迭代器模式中的体现

设计原则 在迭代器模式中的体现
单一职责原则(SRP) 聚合类负责存储,迭代器类负责遍历,职责分离
开闭原则(OCP) 新增遍历方式(如反向迭代器)只需增加迭代器子类,无需修改聚合类
里氏替换原则(LSP) 任何聚合类的迭代器都可替换 Iterator 接口
依赖倒置原则(DIP) 客户端依赖抽象 IteratorIterable,不依赖具体聚合
接口隔离原则(ISP) Iterator 接口只定义 hasNext()/next()/remove(),精简
迪米特法则(LoD) 客户端只与迭代器交互,无须了解聚合内部结构

《Java 23 种设计模式:从踩坑到精通》快速导航

🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀 下一篇:中介者模式:对象关系太乱?请一位“中间人”!🚧 即将发布,敬请关注!

📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。

更多推荐