Java 23 种设计模式:从踩坑到精通 | 迭代器模式 —— 遍历集合,为什么不直接暴露内部结构?
Java 23 种设计模式:从踩坑到精通 | 迭代器模式 —— 遍历集合,为什么不直接暴露内部结构?
摘要:当需要遍历一个聚合对象(如集合、列表、树),但又不想暴露其内部表示时,直接在聚合类上添加遍历方法会导致职责过重、耦合增加。迭代器模式通过将遍历行为封装为独立的迭代器对象,提供一种统一的方式来顺序访问聚合中的每个元素,而无需关心底层是数组、链表还是树。本文从自定义集合的遍历需求出发,完整讲解迭代器模式的原理、UML、代码实现、与 for-each 语法糖的关系,并结合 JDK 集合框架、数据库游标、MyBatis Cursor 等应用,帮你理解“遍历与数据分离”的设计思想。
🗺️ 本文阅读地图(3 分钟速览)
- 为什么直接暴露
get(index)是危险的?- 迭代器模式四大角色拆解
- 手写一个支持正向/反向遍历的自定义集合
- for-each 语法糖底层原理
- JDK
Iterator/ 数据库游标 / MyBatis Cursor 如何体现- 面试必问:
Iterator和Iterable有什么区别?
📖 《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.Iterator 和 java.lang.Iterable 是迭代器模式最经典的实现。ArrayList、LinkedList、HashSet 等所有集合类都通过 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 的
Iterator和Iterable有什么区别? →Iterator是迭代器本身,Iterable是能够返回迭代器的聚合对象。 - 为什么需要迭代器模式? → 分离遍历与存储,提供统一遍历接口,支持多种遍历方式。
面试官追问
- “如何在 for-each 中删除元素?”
👉 不能直接调用list.remove(),会抛ConcurrentModificationException,必须使用Iterator.remove()。 - “迭代器模式如何支持多种遍历方式?”
👉 同一个聚合类提供多个createXxxIterator()方法,分别返回正向、反向、条件过滤等不同的迭代器实现。 - “
ConcurrentModificationException是怎么检测出来的?”
👉 集合内部维护一个modCount,迭代器创建时记录expectedModCount,每次操作时比较两者是否一致。
🎉 恭喜:如果你能立刻说出
Iterator和Iterable的区别,并理解 for-each 语法糖的底层原理,你已经掌握了 Java 中最基础也最重要的设计模式之一。
9. 六大设计原则在迭代器模式中的体现
| 设计原则 | 在迭代器模式中的体现 |
|---|---|
| 单一职责原则(SRP) | 聚合类负责存储,迭代器类负责遍历,职责分离 |
| 开闭原则(OCP) | 新增遍历方式(如反向迭代器)只需增加迭代器子类,无需修改聚合类 |
| 里氏替换原则(LSP) | 任何聚合类的迭代器都可替换 Iterator 接口 |
| 依赖倒置原则(DIP) | 客户端依赖抽象 Iterator 和 Iterable,不依赖具体聚合 |
| 接口隔离原则(ISP) | Iterator 接口只定义 hasNext()/next()/remove(),精简 |
| 迪米特法则(LoD) | 客户端只与迭代器交互,无须了解聚合内部结构 |
《Java 23 种设计模式:从踩坑到精通》快速导航
- 开篇:系列介绍与目录
- 上一篇:解释器模式 —— 自己动手写一个小语言解释器
- 当前:迭代器模式—— 遍历集合,为什么不直接暴露内部结构?(你在这里)
- 下一篇:中介者模式—— 对象关系太乱?请一位“中间人” 🚧 即将发布
- 创建型模式汇总:单例、工厂、建造者、原型
- 结构型模式汇总:适配器、装饰器、代理……
- 行为型模式汇总:观察者、策略、模板方法……
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀 下一篇:中介者模式:对象关系太乱?请一位“中间人”!🚧 即将发布,敬请关注!
📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。
更多推荐


所有评论(0)