Java 23 种设计模式:从踩坑到精通 | 迭代器模式 —— 遍历集合,为什么不直接暴露内部结构?
Java 23 种设计模式:从踩坑到精通 | 迭代器模式 —— 遍历集合,为什么不直接暴露内部结构?
摘要:当需要遍历一个聚合对象(如集合、列表、树),但又不想暴露其内部表示时,直接在聚合类上添加遍历方法会导致职责过重、耦合增加。迭代器模式通过将遍历行为封装为独立的迭代器对象,提供一种统一的方式来顺序访问聚合中的每个元素,而无需关心底层是数组、链表还是树。本文从自定义集合的遍历需求出发,完整讲解迭代器模式的原理、UML、代码实现、与 for-each 语法糖的关系,并结合 JDK 集合框架、数据库游标、MyBatis Cursor 等应用,帮你理解“遍历与数据分离”的设计思想。
🗺️本文阅读地图(3 分钟速览)
- 为什么直接暴露
get(index)是危险的?- 迭代器模式四大角色拆解
- 手写一个支持正向/反向遍历的自定义集合
- for-each 语法糖底层原理
- JDK
Iterator/ 数据库游标 / MyBatis Cursor 如何体现- 面试必问:
Iterator和Iterable有什么区别?
📖《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 上一篇:解释器模式 |当前:迭代器模式| 下一篇:中介者模式
🔗 返回系列总目录
文章目录
- Java 23 种设计模式:从踩坑到精通 | 迭代器模式 —— 遍历集合,为什么不直接暴露内部结构?
- 1. 从“遍历一个自定义列表”的纠结说起
- 1.1 你的场景该不该用迭代器?
- 2. 模式定义与 UML 结构
- 图文解析(配合上述 UML 图)
- 3. 代码实现:自定义列表的迭代器
- 3.1 抽象迭代器
- 3.2 抽象聚合
- 3.3 具体聚合:数组列表
- 3.4 客户端调用
- 4. 扩展:支持反向遍历的内部类迭代器
- 5. 迭代器模式 vs for-each 语法糖
- 6. 优缺点一览
- 7. 框架与实践中的应用
- 7.1 JDK 集合框架
- 7.2 数据库结果集游标
- 7.3 MyBatis 的 Cursor
- 8. 面试必问 + 面试官追问连环炮
- 9. 六大设计原则在迭代器模式中的体现
- 《Java 23 种设计模式:从踩坑到精通》快速导航
1. 从“遍历一个自定义列表”的纠结说起
假设你实现了一个自定义的列表类MyList,内部使用数组存储数据。现在客户端需要遍历列表中的所有元素。最简单的方法是在MyList中直接暴露一个get(index)方法,客户端通过索引循环:
for(inti=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 抽象迭代器
publicinterfaceIterator<T>{booleanhasNext();Tnext();defaultvoidremove(){thrownewUnsupportedOperationException("remove");}}💬白话:迭代器必须能回答“还有下一个吗?”和“把下一个给我”。
3.2 抽象聚合
publicinterfaceIterableCollection<T>{Iterator<T>createIterator();}💬白话:所有集合都必须能“创建一个迭代器出来”。
3.3 具体聚合:数组列表
publicclassArrayBasedList<T>implementsIterableCollection<T>{privateObject[]elements;privateintsize;privatestaticfinalintINIT_CAPACITY=16;publicArrayBasedList(){elements=newObject[INIT_CAPACITY];size=0;}publicvoidadd(Titem){if(size==elements.length){elements=Arrays.copyOf(elements,size*2);}elements[size++]=item;}publicintsize(){returnsize;}@OverridepublicIterator<T>createIterator(){returnnewArrayBasedIterator();}// 具体迭代器作为内部类,可以访问外部聚合的私有字段privateclassArrayBasedIteratorimplementsIterator<T>{privateintcursor=0;@OverridepublicbooleanhasNext(){returncursor<size;}@Override@SuppressWarnings("unchecked")publicTnext(){if(!hasNext())thrownewNoSuchElementException();return(T)elements[cursor++];}}}💬白话:迭代器是聚合的“内部员工”,可以直接访问聚合的私有数组,但对外只暴露
hasNext()和next()。
3.4 客户端调用
ArrayBasedList<String>names=newArrayBasedList<>();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 中新增方法publicIterator<T>createReverseIterator(){returnnewReverseIterator();}privateclassReverseIteratorimplementsIterator<T>{privateintcursor=size-1;@OverridepublicbooleanhasNext(){returncursor>=0;}@Override@SuppressWarnings("unchecked")publicTnext(){if(!hasNext())thrownewNoSuchElementException();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(Strings:names){System.out.println(s);}// 编译后等效代码Iterator<String>it=names.iterator();while(it.hasNext()){Strings=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=newArrayList<>();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(Useruser: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智能调度实战》。技术相通,思路可鉴。
