当前位置: 首页 > news >正文

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

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

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

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

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

📖《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.Iteratorjava.lang.Iterable是迭代器模式最经典的实现。ArrayListLinkedListHashSet等所有集合类都通过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 的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智能调度实战》。技术相通,思路可鉴。

http://www.gsyq.cn/news/1617344.html

相关文章:

  • Jamba混合架构原理:Mamba+Transformer+MoE协同机制解析
  • Spring漏洞自动化工具:设计原理与红队实战指南
  • 基于IIM-42652和MK60DN512的6DoF运动跟踪系统设计
  • GPT-4参数量与2%激活率的真相:MoE架构下的三层参数定义
  • 基于JMeter与华为云的Dify智能客服压力测试实战指南
  • AMAT 0190-16825可控硅功率控制器
  • OneMore插件:让OneNote笔记效率提升10倍的终极解决方案
  • 大模型中间层归零:确定性推理如何重构LLM工程实践
  • 终极Windows按键映射指南:QKeyMapper让游戏和办公效率翻倍
  • Appshark静态污点分析:Android应用安全自动化审计实战指南
  • Python后端Web安全实战:从注入防御到文件上传的深度防护指南
  • LLM控制系统中的门控、审批与人在环中三大安全模式
  • JMeter并发测试实战:从核心概念到性能瓶颈定位
  • Python自动化安全审计:Bandit与Pyt工具实战指南
  • contenteditable富文本编辑器的XSS安全防护实战指南
  • 构建安全资源下载器:从证书信任到完整性校验的实战指南
  • 塞尔达传说旷野之息存档编辑器终极指南:10分钟掌握海拉鲁世界修改技巧
  • Android Native代码深度防护:从源码混淆到自定义加壳的实战指南
  • 基于Web Crypto API的AES-GCM文件加密实战指南
  • 2026年知网AIGC检测又升级了!4个免费降AI工具把论文AI率压到5%以下(亲测62.7%→5.8%)
  • Nginx安全配置实战:防御SQL注入与目录遍历攻击
  • Mythos能力跃迁:AI叙事生成与情感推理技术解析
  • LLM论文技术雷达:从arXiv筛选到生产落地的工程化方法论
  • C语言枚举(enum)详解:别被“枚举”吓到,它就是整数换了个马甲
  • Claude 3.5 Sonnet隐式推理压缩技术解析
  • Java实战SM2国密算法:从Bouncy Castle集成到签名验签全流程
  • League Akari:英雄联盟终极工具箱 - 免费智能助手完整指南
  • 人生非完美主义的具象化的庖丁解牛
  • Mythos模型三大能力跃迁:推理稳定性、多跳因果与跨文档一致性
  • iOS逆向入门:使用Clutch为微信砸壳与Cryptid验证全流程