欢迎光临
我们一直在努力

【从零入门23种设计模式16】行为型之迭代器模式

一、迭代器模式核心定义

迭代器模式是行为型设计模式的一种,核心目的是:

提供一种方法顺序访问一个聚合对象(如集合、容器)中的各个元素,而又不暴露该对象的内部表示。简单来说,就是把集合的 “遍历逻辑” 从集合类中抽离出来,封装成独立的迭代器对象。

核心解决的问题
  • 解耦遍历与集合:集合只需关注 “存储元素”,遍历逻辑交给迭代器,符合单一职责原则;
  • 统一遍历接口:不同集合(如数组、链表、树)都可通过相同的迭代器接口遍历,无需关心集合底层实现;
  • 支持多线程遍历:每个迭代器持有独立的遍历状态,多个迭代器可同时遍历同一个集合;
  • 安全遍历:迭代器可控制遍历过程(如禁止遍历中修改集合),避免并发修改异常。
  • 生活类比
    • 场景 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遍历 BeangetBeansOfType()返回的集合可通过迭代器遍历。

    六、迭代器模式 vs 访问者模式(易混淆点)

    维度迭代器模式访问者模式
    核心目的 遍历集合中的元素(不修改元素) 操作集合中的元素(修改 / 处理元素)
    核心结构 迭代器接口 + 聚合接口 访问者接口 + 元素接口
    关注点 遍历的 “方式”(如何遍历) 元素的 “操作”(对元素做什么)
    典型场景 遍历集合、分页查询、数据读取 元素操作、数据导出、规则校验

    总结

  • 迭代器模式的核心是将集合的遍历逻辑抽离为独立的迭代器对象,实现遍历与存储的解耦;
  • 核心角色包括迭代器接口(统一遍历方法)、具体迭代器(封装遍历逻辑)、聚合接口(创建迭代器)、具体聚合(存储元素);
  • 业务开发中,迭代器模式最实用的场景是分页遍历大数据集合(如数据库分页、分批接口调用),可避免一次性加载大量数据;
  • JDK 中所有集合类都基于迭代器模式实现,Iterator接口是遍历所有集合的标准方式,foreach 循环底层也是调用迭代器。
  • 赞(0)
    未经允许不得转载:171主机测评 » 【从零入门23种设计模式16】行为型之迭代器模式
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址