Java 23 种设计模式从踩坑到精通 | 迭代器模式 —— 遍历集合为什么不直接暴露内部结构摘要当需要遍历一个聚合对象如集合、列表、树但又不想暴露其内部表示时直接在聚合类上添加遍历方法会导致职责过重、耦合增加。迭代器模式通过将遍历行为封装为独立的迭代器对象提供一种统一的方式来顺序访问聚合中的每个元素而无需关心底层是数组、链表还是树。本文从自定义集合的遍历需求出发完整讲解迭代器模式的原理、UML、代码实现、与 for-each 语法糖的关系并结合 JDK 集合框架、数据库游标、MyBatis Cursor 等应用帮你理解“遍历与数据分离”的设计思想。️本文阅读地图3 分钟速览为什么直接暴露get(index)是危险的迭代器模式四大角色拆解手写一个支持正向/反向遍历的自定义集合for-each 语法糖底层原理JDKIterator/ 数据库游标 / 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 的 Cursor8. 面试必问 面试官追问连环炮9. 六大设计原则在迭代器模式中的体现《Java 23 种设计模式从踩坑到精通》快速导航1. 从“遍历一个自定义列表”的纠结说起假设你实现了一个自定义的列表类MyList内部使用数组存储数据。现在客户端需要遍历列表中的所有元素。最简单的方法是在MyList中直接暴露一个get(index)方法客户端通过索引循环for(inti0;imyList.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 抽象迭代器publicinterfaceIteratorT{booleanhasNext();Tnext();defaultvoidremove(){thrownewUnsupportedOperationException(remove);}}白话迭代器必须能回答“还有下一个吗”和“把下一个给我”。3.2 抽象聚合publicinterfaceIterableCollectionT{IteratorTcreateIterator();}白话所有集合都必须能“创建一个迭代器出来”。3.3 具体聚合数组列表publicclassArrayBasedListTimplementsIterableCollectionT{privateObject[]elements;privateintsize;privatestaticfinalintINIT_CAPACITY16;publicArrayBasedList(){elementsnewObject[INIT_CAPACITY];size0;}publicvoidadd(Titem){if(sizeelements.length){elementsArrays.copyOf(elements,size*2);}elements[size]item;}publicintsize(){returnsize;}OverridepublicIteratorTcreateIterator(){returnnewArrayBasedIterator();}// 具体迭代器作为内部类可以访问外部聚合的私有字段privateclassArrayBasedIteratorimplementsIteratorT{privateintcursor0;OverridepublicbooleanhasNext(){returncursorsize;}OverrideSuppressWarnings(unchecked)publicTnext(){if(!hasNext())thrownewNoSuchElementException();return(T)elements[cursor];}}}白话迭代器是聚合的“内部员工”可以直接访问聚合的私有数组但对外只暴露hasNext()和next()。3.4 客户端调用ArrayBasedListStringnamesnewArrayBasedList();names.add(Alice);names.add(Bob);names.add(Charlie);IteratorStringitnames.createIterator();while(it.hasNext()){System.out.println(it.next());}客户端只需要通过迭代器遍历完全不知道底层是数组。如果将来改为链表实现只需修改ArrayBasedList内部客户端代码无需任何改动。4. 扩展支持反向遍历的内部类迭代器// 在 ArrayBasedList 中新增方法publicIteratorTcreateReverseIterator(){returnnewReverseIterator();}privateclassReverseIteratorimplementsIteratorT{privateintcursorsize-1;OverridepublicbooleanhasNext(){returncursor0;}OverrideSuppressWarnings(unchecked)publicTnext(){if(!hasNext())thrownewNoSuchElementException();return(T)elements[cursor--];}}白话同一个聚合可以提供多种迭代器——正向遍历、反向遍历、条件过滤遍历……聚合只负责创建迭代器遍历逻辑完全由迭代器控制。客户端调用IteratorStringreverseItnames.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);}// 编译后等效代码IteratorStringitnames.iterator();while(it.hasNext()){Stringsit.next();System.out.println(s);}白话你每天都在用迭代器只是 Java 帮你偷偷写好了while循环。6. 优缺点一览优点缺点封装性不暴露聚合对象的内部结构增加类数量每个聚合类通常对应一个迭代器类多遍历支持同时可以存在多个迭代器互不干扰简单集合可能过于复杂引入额外的抽象层统一接口遍历算法统一客户端无须关注数据结构差异迭代器的remove()需要小心并发修改时可能抛异常符合单一职责聚合类只负责存储迭代器负责遍历7. 框架与实践中的应用7.1 JDK 集合框架java.util.Iterator和java.lang.Iterable是迭代器模式最经典的实现。ArrayList、LinkedList、HashSet等所有集合类都通过iterator()方法返回对应的迭代器。ListStringlistnewArrayList();list.add(A);list.add(B);IteratorStringitlist.iterator();while(it.hasNext()){System.out.println(it.next());}7.2 数据库结果集游标JDBC 的ResultSet本质上就是一个迭代器。它提供next()方法逐行遍历查询结果getString()、getInt()等方法获取当前行字段值完全屏蔽了数据库内部的数据结构。7.3 MyBatis 的 CursorMyBatis 3.5 引入的Cursor接口继承了Iterable允许对大量查询结果进行流式迭代处理避免一次性加载所有数据到内存。try(CursorUsercursorsqlSession.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不依赖具体聚合接口隔离原则ISPIterator接口只定义hasNext()/next()/remove()精简迪米特法则LoD客户端只与迭代器交互无须了解聚合内部结构《Java 23 种设计模式从踩坑到精通》快速导航开篇系列介绍与目录上一篇解释器模式 —— 自己动手写一个小语言解释器当前迭代器模式—— 遍历集合为什么不直接暴露内部结构你在这里下一篇中介者模式—— 对象关系太乱请一位“中间人” 即将发布创建型模式汇总单例、工厂、建造者、原型结构型模式汇总适配器、装饰器、代理……行为型模式汇总观察者、策略、模板方法…… 关注《Java 23 种设计模式从踩坑到精通》用 25 篇文章彻底吃透设计模式。福利预告全系列代码及 UML 源码将在完结时统一打包开放点击「关注」「收藏」第一时间获取。下一篇中介者模式对象关系太乱请一位“中间人” 即将发布敬请关注 除了设计模式我也在深挖智能物流实战WMS、托盘调度、机器学习落地。欢迎点击头像看看专栏 《出版社物流WMS智能调度实战》。技术相通思路可鉴。
Java 23 种设计模式:从踩坑到精通 | 迭代器模式 —— 遍历集合,为什么不直接暴露内部结构?
发布时间:2026/7/1 23:21:31
Java 23 种设计模式从踩坑到精通 | 迭代器模式 —— 遍历集合为什么不直接暴露内部结构摘要当需要遍历一个聚合对象如集合、列表、树但又不想暴露其内部表示时直接在聚合类上添加遍历方法会导致职责过重、耦合增加。迭代器模式通过将遍历行为封装为独立的迭代器对象提供一种统一的方式来顺序访问聚合中的每个元素而无需关心底层是数组、链表还是树。本文从自定义集合的遍历需求出发完整讲解迭代器模式的原理、UML、代码实现、与 for-each 语法糖的关系并结合 JDK 集合框架、数据库游标、MyBatis Cursor 等应用帮你理解“遍历与数据分离”的设计思想。️本文阅读地图3 分钟速览为什么直接暴露get(index)是危险的迭代器模式四大角色拆解手写一个支持正向/反向遍历的自定义集合for-each 语法糖底层原理JDKIterator/ 数据库游标 / 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 的 Cursor8. 面试必问 面试官追问连环炮9. 六大设计原则在迭代器模式中的体现《Java 23 种设计模式从踩坑到精通》快速导航1. 从“遍历一个自定义列表”的纠结说起假设你实现了一个自定义的列表类MyList内部使用数组存储数据。现在客户端需要遍历列表中的所有元素。最简单的方法是在MyList中直接暴露一个get(index)方法客户端通过索引循环for(inti0;imyList.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 抽象迭代器publicinterfaceIteratorT{booleanhasNext();Tnext();defaultvoidremove(){thrownewUnsupportedOperationException(remove);}}白话迭代器必须能回答“还有下一个吗”和“把下一个给我”。3.2 抽象聚合publicinterfaceIterableCollectionT{IteratorTcreateIterator();}白话所有集合都必须能“创建一个迭代器出来”。3.3 具体聚合数组列表publicclassArrayBasedListTimplementsIterableCollectionT{privateObject[]elements;privateintsize;privatestaticfinalintINIT_CAPACITY16;publicArrayBasedList(){elementsnewObject[INIT_CAPACITY];size0;}publicvoidadd(Titem){if(sizeelements.length){elementsArrays.copyOf(elements,size*2);}elements[size]item;}publicintsize(){returnsize;}OverridepublicIteratorTcreateIterator(){returnnewArrayBasedIterator();}// 具体迭代器作为内部类可以访问外部聚合的私有字段privateclassArrayBasedIteratorimplementsIteratorT{privateintcursor0;OverridepublicbooleanhasNext(){returncursorsize;}OverrideSuppressWarnings(unchecked)publicTnext(){if(!hasNext())thrownewNoSuchElementException();return(T)elements[cursor];}}}白话迭代器是聚合的“内部员工”可以直接访问聚合的私有数组但对外只暴露hasNext()和next()。3.4 客户端调用ArrayBasedListStringnamesnewArrayBasedList();names.add(Alice);names.add(Bob);names.add(Charlie);IteratorStringitnames.createIterator();while(it.hasNext()){System.out.println(it.next());}客户端只需要通过迭代器遍历完全不知道底层是数组。如果将来改为链表实现只需修改ArrayBasedList内部客户端代码无需任何改动。4. 扩展支持反向遍历的内部类迭代器// 在 ArrayBasedList 中新增方法publicIteratorTcreateReverseIterator(){returnnewReverseIterator();}privateclassReverseIteratorimplementsIteratorT{privateintcursorsize-1;OverridepublicbooleanhasNext(){returncursor0;}OverrideSuppressWarnings(unchecked)publicTnext(){if(!hasNext())thrownewNoSuchElementException();return(T)elements[cursor--];}}白话同一个聚合可以提供多种迭代器——正向遍历、反向遍历、条件过滤遍历……聚合只负责创建迭代器遍历逻辑完全由迭代器控制。客户端调用IteratorStringreverseItnames.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);}// 编译后等效代码IteratorStringitnames.iterator();while(it.hasNext()){Stringsit.next();System.out.println(s);}白话你每天都在用迭代器只是 Java 帮你偷偷写好了while循环。6. 优缺点一览优点缺点封装性不暴露聚合对象的内部结构增加类数量每个聚合类通常对应一个迭代器类多遍历支持同时可以存在多个迭代器互不干扰简单集合可能过于复杂引入额外的抽象层统一接口遍历算法统一客户端无须关注数据结构差异迭代器的remove()需要小心并发修改时可能抛异常符合单一职责聚合类只负责存储迭代器负责遍历7. 框架与实践中的应用7.1 JDK 集合框架java.util.Iterator和java.lang.Iterable是迭代器模式最经典的实现。ArrayList、LinkedList、HashSet等所有集合类都通过iterator()方法返回对应的迭代器。ListStringlistnewArrayList();list.add(A);list.add(B);IteratorStringitlist.iterator();while(it.hasNext()){System.out.println(it.next());}7.2 数据库结果集游标JDBC 的ResultSet本质上就是一个迭代器。它提供next()方法逐行遍历查询结果getString()、getInt()等方法获取当前行字段值完全屏蔽了数据库内部的数据结构。7.3 MyBatis 的 CursorMyBatis 3.5 引入的Cursor接口继承了Iterable允许对大量查询结果进行流式迭代处理避免一次性加载所有数据到内存。try(CursorUsercursorsqlSession.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不依赖具体聚合接口隔离原则ISPIterator接口只定义hasNext()/next()/remove()精简迪米特法则LoD客户端只与迭代器交互无须了解聚合内部结构《Java 23 种设计模式从踩坑到精通》快速导航开篇系列介绍与目录上一篇解释器模式 —— 自己动手写一个小语言解释器当前迭代器模式—— 遍历集合为什么不直接暴露内部结构你在这里下一篇中介者模式—— 对象关系太乱请一位“中间人” 即将发布创建型模式汇总单例、工厂、建造者、原型结构型模式汇总适配器、装饰器、代理……行为型模式汇总观察者、策略、模板方法…… 关注《Java 23 种设计模式从踩坑到精通》用 25 篇文章彻底吃透设计模式。福利预告全系列代码及 UML 源码将在完结时统一打包开放点击「关注」「收藏」第一时间获取。下一篇中介者模式对象关系太乱请一位“中间人” 即将发布敬请关注 除了设计模式我也在深挖智能物流实战WMS、托盘调度、机器学习落地。欢迎点击头像看看专栏 《出版社物流WMS智能调度实战》。技术相通思路可鉴。