一、迭代器模式核心定义
迭代器模式是行为型设计模式的一种,核心目的是:
提供一种方法顺序访问一个聚合对象(如集合、容器)中的各个元素,而又不暴露该对象的内部表示。简单来说,就是把集合的 "遍历逻辑" 从集合类中抽离出来,封装成独立的迭代器对象。
核心解决的问题
- 解耦遍历与集合 :集合只需关注 "存储元素",遍历逻辑交给迭代器,符合单一职责原则;
- 统一遍历接口 :不同集合(如数组、链表、树)都可通过相同的迭代器接口遍历,无需关心集合底层实现;
- 支持多线程遍历 :每个迭代器持有独立的遍历状态,多个迭代器可同时遍历同一个集合;
- 安全遍历 :迭代器可控制遍历过程(如禁止遍历中修改集合),避免并发修改异常。
生活类比
- 场景 1 :图书馆借书
- 聚合对象:图书馆(存储书籍);
- 迭代器:借书员(按编号 / 分类遍历书籍,无需知道图书馆的书架布局);
- 核心:你只需告诉借书员 "找所有计算机类书籍",无需自己翻书架。
- 场景 2 :点餐系统
- 聚合对象:菜单(存储菜品);
- 迭代器:服务员(按热菜 / 凉菜 / 主食遍历菜品,无需知道菜单的存储格式)。
标准角色
| 角色 | 职责 | 类比(图书馆场景) | JDK 对应类 / 接口 |
|---|---|---|---|
| 迭代器接口(Iterator) | 定义遍历元素的统一方法(hasNext()、next()、remove()) |
借书员的工作规范 | java.util.Iterator |
| 具体迭代器(ConcreteIterator) | 实现迭代器接口,持有聚合对象引用,记录遍历状态 | 具体的借书员(按编号遍历) | ArrayList.Itr、HashMap.KeyIterator |
| 聚合接口(Aggregate) | 定义创建迭代器的方法(createIterator()) |
图书馆的 "提供遍历服务" 规范 | java.util.Collection |
| 具体聚合(ConcreteAggregate) | 实现聚合接口,返回具体迭代器实例 | 具体的图书馆(存储书籍) | ArrayList、HashMap、LinkedList |
核心 UML 类图

二、自定义集合 + 迭代器
以 "自定义书架集合" 为例,实现迭代器模式的核心逻辑 ------ 这是理解 JDK 集合迭代器的基础。
1. 步骤 1:定义迭代器接口(统一遍历规范)
/**
* 迭代器接口:定义遍历书籍的统一方法
*/
public interface BookIterator {
/**
* 判断是否有下一本书
*/
boolean hasNext();
/**
* 获取下一本书
*/
Book next();
/**
* 移除当前遍历的书(可选)
*/
void remove();
}
2. 步骤 2:定义聚合接口(创建迭代器)
/**
* 聚合接口:书架的统一规范,定义创建迭代器的方法
*/
public interface BookShelfAggregate {
/**
* 添加书籍
*/
void addBook(Book book);
/**
* 删除书籍
*/
void removeBook(Book book);
/**
* 创建迭代器(核心:聚合对象提供迭代器)
*/
BookIterator createIterator();
}
3. 步骤 3:实现具体聚合(自定义书架)
import java.util.ArrayList;
import java.util.List;
/**
* 具体聚合:书架(存储书籍的集合)
*/
public class BookShelf implements BookShelfAggregate {
// 底层存储:ArrayList(也可替换为数组、链表等,迭代器无需感知)
private final List<Book> books = new ArrayList<>();
@Override
public void addBook(Book book) {
books.add(book);
}
@Override
public void removeBook(Book book) {
books.remove(book);
}
/**
* 返回具体迭代器(绑定当前书架)
*/
@Override
public BookIterator createIterator() {
return new BookShelfIterator(this);
}
// 供迭代器访问内部元素的方法(不暴露底层List)
public Book getBook(int index) {
return books.get(index);
}
// 供迭代器获取集合大小
public int getSize() {
return books.size();
}
}
4. 步骤 4:实现具体迭代器(书架迭代器)
/**
* 具体迭代器:书架迭代器(封装遍历逻辑)
*/
public class BookShelfIterator implements BookIterator {
// 持有聚合对象引用(遍历的目标集合)
private final BookShelf bookShelf;
// 遍历状态:当前索引
private int currentIndex = 0;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
}
/**
* 判断是否有下一个元素(核心:遍历终止条件)
*/
@Override
public boolean hasNext() {
return currentIndex < bookShelf.getSize();
}
/**
* 获取下一个元素(核心:移动索引并返回元素)
*/
@Override
public Book next() {
if (!hasNext()) {
throw new NoSuchElementException("已遍历到最后一本书");
}
Book book = bookShelf.getBook(currentIndex);
currentIndex++; // 移动索引
return book;
}
/**
* 移除当前元素(需回退索引,避免漏遍历)
*/
@Override
public void remove() {
if (currentIndex <= 0) {
throw new IllegalStateException("未开始遍历或已移除当前元素");
}
bookShelf.removeBook(bookShelf.getBook(currentIndex - 1));
currentIndex--; // 回退索引
}
}
5. 步骤 5:书籍模型(迭代的元素)
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 迭代的元素:书籍
*/
@Data
@AllArgsConstructor
public class Book {
private String isbn; // 书号
private String name; // 书名
private String author; // 作者
}
6. 客户端(使用迭代器遍历)
import java.util.NoSuchElementException;
/**
* 客户端:使用迭代器遍历书架
*/
public class IteratorClient {
public static void main(String[] args) {
// 1. 创建聚合对象(书架)并添加元素
BookShelf bookShelf = new BookShelf();
bookShelf.addBook(new Book("9787111641247", "Java编程思想", "Bruce Eckel"));
bookShelf.addBook(new Book("9787121387374", "Spring实战", "Craig Walls"));
bookShelf.addBook(new Book("9787115546081", "设计模式之美", "王争"));
// 2. 创建迭代器(无需关心书架底层是List/数组)
BookIterator iterator = bookShelf.createIterator();
// 3. 遍历元素(统一接口,适配所有聚合对象)
System.out.println("======= 遍历所有书籍 =======");
while (iterator.hasNext()) {
Book book = iterator.next();
System.out.println("书号:" + book.getIsbn() + ",书名:" + book.getName() + ",作者:" + book.getAuthor());
}
// 4. 测试移除元素(遍历中删除)
System.out.println("\n======= 移除第二本书后重新遍历 =======");
BookIterator iterator2 = bookShelf.createIterator();
// 跳过第一本
iterator2.next();
// 移除第二本
iterator2.remove();
// 重新遍历
while (iterator2.hasNext()) {
Book book = iterator2.next();
System.out.println("书号:" + book.getIsbn() + ",书名:" + book.getName() + ",作者:" + book.getAuthor());
}
// 5. 测试遍历到末尾(抛出异常)
try {
BookIterator iterator3 = bookShelf.createIterator();
while (iterator3.hasNext()) {
iterator3.next();
}
iterator3.next(); // 无元素,抛出异常
} catch (NoSuchElementException e) {
System.out.println("\n遍历异常:" + e.getMessage());
}
}
}
输出结果
======= 遍历所有书籍 =======
书号:9787111641247,书名:Java编程思想,作者:Bruce Eckel
书号:9787121387374,书名:Spring实战,作者:Craig Walls
书号:9787115546081,书名:设计模式之美,作者:王争
======= 移除第二本书后重新遍历 =======
书号:9787115546081,书名:设计模式之美,作者:王争
遍历异常:已遍历到最后一本书
三、Spring 实战版(自定义分页迭代器)
在业务开发中,迭代器模式最实用的场景是分页遍历大数据集合(如数据库分页查询、接口分批拉取数据)。以下实现一个 "数据库用户列表分页迭代器",无需一次性加载所有数据,而是按需分页获取。
1. 依赖准备(Spring Boot)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.2.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2. 核心模型定义
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 迭代的元素:用户
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String username;
private String email;
}
/**
* 分页查询参数
*/
@Data
@AllArgsConstructor
public class PageParam {
private int pageNum; // 页码(从1开始)
private int pageSize; // 每页条数
}
/**
* 分页查询结果
*/
@Data
public class PageResult<T> {
private int pageNum; // 当前页码
private int pageSize; // 每页条数
private long total; // 总条数
private List<T> data; // 当前页数据
// 判断是否有下一页
public boolean hasNext() {
return pageNum * pageSize < total;
}
}
3. 迭代器接口(分页迭代器)
/**
* 分页迭代器接口:按需分页遍历数据
*/
public interface PageIterator<T> {
/**
* 判断是否有下一页数据
*/
boolean hasNextPage();
/**
* 获取下一个元素(跨页自动加载)
*/
T next();
/**
* 获取当前页所有元素
*/
List<T> currentPageData();
/**
* 关闭迭代器(释放资源)
*/
void close();
}
4. 具体迭代器(用户分页迭代器)
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 具体迭代器:用户分页迭代器(按需加载数据库数据)
*/
@Slf4j
@Component
public class UserPageIterator implements PageIterator<User> {
// 依赖数据服务(模拟数据库查询)
private final UserDataService userDataService;
// 分页参数
private final int pageSize;
// 当前页数据迭代器
private Iterator<User> currentPageIterator;
// 当前页码
private int currentPageNum = 1;
// 总条数(首次查询后缓存)
private long total = -1;
// 是否已关闭
private boolean closed = false;
public UserPageIterator(UserDataService userDataService) {
this.userDataService = userDataService;
this.pageSize = 2; // 每页2条(可配置)
// 加载第一页数据
loadPageData(currentPageNum);
}
/**
* 加载指定页码的数据
*/
private void loadPageData(int pageNum) {
if (closed) {
throw new IllegalStateException("迭代器已关闭");
}
log.info("加载第{}页数据,每页{}条", pageNum, pageSize);
PageResult<User> pageResult = userDataService.queryUserByPage(new PageParam(pageNum, pageSize));
// 缓存总条数
if (total == -1) {
total = pageResult.getTotal();
}
// 初始化当前页迭代器
currentPageIterator = pageResult.getData().iterator();
currentPageNum = pageNum;
}
/**
* 判断是否有下一页数据
*/
@Override
public boolean hasNextPage() {
if (closed) {
return false;
}
// 当前页还有元素 → 有下一个
if (currentPageIterator.hasNext()) {
return true;
}
// 当前页无元素,判断是否有下一页
PageParam param = new PageParam(currentPageNum, pageSize);
PageResult<User> currentPage = userDataService.queryUserByPage(param);
return currentPage.hasNext();
}
/**
* 获取下一个元素(跨页自动加载)
*/
@Override
public User next() {
if (closed) {
throw new IllegalStateException("迭代器已关闭");
}
// 当前页无元素,加载下一页
if (!currentPageIterator.hasNext()) {
if (!hasNextPage()) {
throw new NoSuchElementException("已遍历完所有用户");
}
loadPageData(currentPageNum + 1);
}
return currentPageIterator.next();
}
/**
* 获取当前页所有数据
*/
@Override
public List<User> currentPageData() {
if (closed) {
return new ArrayList<>();
}
PageResult<User> currentPage = userDataService.queryUserByPage(new PageParam(currentPageNum, pageSize));
return currentPage.getData();
}
/**
* 关闭迭代器(释放资源)
*/
@Override
public void close() {
log.info("关闭用户分页迭代器");
closed = true;
currentPageIterator = null;
}
}
5. 数据服务(模拟数据库查询)
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 数据服务:模拟数据库分页查询用户
*/
@Service
public class UserDataService {
// 模拟数据库中的用户数据
private final List<User> userList = new ArrayList<>();
public UserDataService() {
// 初始化10个用户
for (long i = 1; i <= 10; i++) {
userList.add(new User(i, "user" + i, "user" + i + "@example.com"));
}
}
/**
* 分页查询用户
*/
public PageResult<User> queryUserByPage(PageParam param) {
int pageNum = param.getPageNum();
int pageSize = param.getPageSize();
// 计算起始索引
int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, userList.size());
// 截取当前页数据
List<User> pageData = new ArrayList<>();
if (start < userList.size()) {
pageData = userList.subList(start, end);
}
// 构建分页结果
PageResult<User> result = new PageResult<>();
result.setPageNum(pageNum);
result.setPageSize(pageSize);
result.setTotal(userList.size());
result.setData(pageData);
return result;
}
}
6. 客户端(Spring Boot 测试)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 客户端:使用分页迭代器遍历用户数据
*/
@SpringBootApplication
public class SpringIteratorDemoApplication {
public static void main(String[] args) {
// 1. 启动Spring容器
ConfigurableApplicationContext context = SpringApplication.run(SpringIteratorDemoApplication.class, args);
UserPageIterator iterator = context.getBean(UserPageIterator.class);
// 2. 遍历所有用户(自动分页加载)
System.out.println("======= 遍历所有用户(自动分页) =======");
int count = 0;
while (iterator.hasNextPage()) {
User user = iterator.next();
count++;
System.out.println("第" + count + "个用户:ID=" + user.getId() + ",用户名=" + user.getUsername());
}
// 3. 测试获取当前页数据
System.out.println("\n======= 获取最后一页数据 =======");
iterator.loadPageData(5); // 跳转到第5页(最后一页)
iterator.currentPageData().forEach(user ->
System.out.println("最后一页用户:ID=" + user.getId() + ",用户名=" + user.getUsername())
);
// 4. 关闭迭代器
iterator.close();
context.close();
}
}
输出结果
======= 遍历所有用户(自动分页) =======
加载第1页数据,每页2条
第1个用户:ID=1,用户名=user1
第2个用户:ID=2,用户名=user2
加载第2页数据,每页2条
第3个用户:ID=3,用户名=user3
第4个用户:ID=4,用户名=user4
加载第3页数据,每页2条
第5个用户:ID=5,用户名=user5
第6个用户:ID=6,用户名=user6
加载第4页数据,每页2条
第7个用户:ID=7,用户名=user7
第8个用户:ID=8,用户名=user8
加载第5页数据,每页2条
第9个用户:ID=9,用户名=user9
第10个用户:ID=10,用户名=user10
======= 获取最后一页数据 =======
加载第5页数据,每页2条
最后一页用户:ID=9,用户名=user9
最后一页用户:ID=10,用户名=user10
关闭用户分页迭代器
四、迭代器模式的核心特点与适用场景
优点
- 解耦遍历与集合:集合只需关注存储,遍历交给迭代器,符合单一职责原则;
- 统一遍历接口:不同集合(数组、链表、树、数据库分页)都可通过相同接口遍历,降低使用成本;
- 支持多态遍历:新增集合类型时,只需新增迭代器类,无需修改遍历代码(开闭原则);
- 按需加载数据:分页迭代器可避免一次性加载大量数据,降低内存占用;
- 安全遍历:迭代器可控制遍历过程(如禁止遍历中修改集合),避免并发修改异常。
缺点
- 增加代码复杂度:简单集合(如固定大小数组)使用迭代器会增加额外类;
- 遍历效率略低:迭代器的封装会带来轻微的性能损耗(可忽略,除非超高频遍历);
- 单向遍历为主:大部分迭代器是单向的(只能往后遍历),双向迭代器实现复杂。
适用场景
- 遍历集合但不想暴露内部结构:如自定义集合类、框架中的容器;
- 统一不同集合的遍历方式:如同时遍历 ArrayList、LinkedList、HashMap;
- 分页遍历大数据:如数据库分页查询、接口分批拉取数据、大数据文件读取;
- 多线程遍历:每个线程持有独立迭代器,避免遍历状态冲突;
- 需要安全遍历:如禁止遍历中修改集合(JDK 的 fail-fast 迭代器)。
五、JDK/ Spring 中的原生应用(必须知道)
迭代器模式是 JDK 中最基础、最常用的设计模式,几乎所有集合类都基于它实现:
1. JDK 核心迭代器
- java.util.Iterator :迭代器核心接口(hasNext()、next()、remove());
- java.util.Collection :聚合接口(iterator()方法创建迭代器);
- ArrayList.Itr :ArrayList 的具体迭代器(内部类,封装数组遍历逻辑);
- HashMap.KeyIterator/ValueIterator :HashMap 的键 / 值迭代器(封装哈希表遍历逻辑);
- Iterable接口 :所有可遍历集合的父接口(支持 foreach 循环,底层调用iterator())。
2. JDK 增强迭代器
- ListIterator :双向迭代器(支持向前 / 向后遍历、添加 / 修改元素);
- Spliterator :拆分迭代器(Java 8 新增,支持并行遍历,适用于流式计算);
- Enumeration :古老的迭代器(JDK 1.0,已被 Iterator 替代)。
3. Spring 中的应用
- org.springframework.util.CollectionUtils :提供统一的迭代器工具方法;
- Spring Data JPA分页迭代 :Pageable/Page结合迭代器实现分页遍历;
- Spring Batch数据读取 :ItemReader基于迭代器模式实现分批读取数据;
- ApplicationContext遍历 Bean :getBeansOfType()返回的集合可通过迭代器遍历。
六、迭代器模式 vs 访问者模式(易混淆点)
| 维度 | 迭代器模式 | 访问者模式 |
|---|---|---|
| 核心目的 | 遍历集合中的元素(不修改元素) | 操作集合中的元素(修改 / 处理元素) |
| 核心结构 | 迭代器接口 + 聚合接口 | 访问者接口 + 元素接口 |
| 关注点 | 遍历的 "方式"(如何遍历) | 元素的 "操作"(对元素做什么) |
| 典型场景 | 遍历集合、分页查询、数据读取 | 元素操作、数据导出、规则校验 |
总结
- 迭代器模式的核心是将集合的遍历逻辑抽离为独立的迭代器对象,实现遍历与存储的解耦;
- 核心角色包括迭代器接口(统一遍历方法)、具体迭代器(封装遍历逻辑)、聚合接口(创建迭代器)、具体聚合(存储元素);
- 业务开发中,迭代器模式最实用的场景是分页遍历大数据集合(如数据库分页、分批接口调用),可避免一次性加载大量数据;
- JDK 中所有集合类都基于迭代器模式实现,
Iterator接口是遍历所有集合的标准方式,foreach 循环底层也是调用迭代器。
