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

一、迭代器模式核心定义

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

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

核心解决的问题
  1. 解耦遍历与集合 :集合只需关注 "存储元素",遍历逻辑交给迭代器,符合单一职责原则;
  2. 统一遍历接口 :不同集合(如数组、链表、树)都可通过相同的迭代器接口遍历,无需关心集合底层实现;
  3. 支持多线程遍历 :每个迭代器持有独立的遍历状态,多个迭代器可同时遍历同一个集合;
  4. 安全遍历 :迭代器可控制遍历过程(如禁止遍历中修改集合),避免并发修改异常。
生活类比
  • 场景 1 :图书馆借书
    • 聚合对象:图书馆(存储书籍);
    • 迭代器:借书员(按编号 / 分类遍历书籍,无需知道图书馆的书架布局);
    • 核心:你只需告诉借书员 "找所有计算机类书籍",无需自己翻书架。
  • 场景 2 :点餐系统
    • 聚合对象:菜单(存储菜品);
    • 迭代器:服务员(按热菜 / 凉菜 / 主食遍历菜品,无需知道菜单的存储格式)。
标准角色
角色 职责 类比(图书馆场景) JDK 对应类 / 接口
迭代器接口(Iterator) 定义遍历元素的统一方法(hasNext()next()remove() 借书员的工作规范 java.util.Iterator
具体迭代器(ConcreteIterator) 实现迭代器接口,持有聚合对象引用,记录遍历状态 具体的借书员(按编号遍历) ArrayList.ItrHashMap.KeyIterator
聚合接口(Aggregate) 定义创建迭代器的方法(createIterator() 图书馆的 "提供遍历服务" 规范 java.util.Collection
具体聚合(ConcreteAggregate) 实现聚合接口,返回具体迭代器实例 具体的图书馆(存储书籍) ArrayListHashMapLinkedList
核心 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
关闭用户分页迭代器

四、迭代器模式的核心特点与适用场景

优点
  1. 解耦遍历与集合:集合只需关注存储,遍历交给迭代器,符合单一职责原则;
  2. 统一遍历接口:不同集合(数组、链表、树、数据库分页)都可通过相同接口遍历,降低使用成本;
  3. 支持多态遍历:新增集合类型时,只需新增迭代器类,无需修改遍历代码(开闭原则);
  4. 按需加载数据:分页迭代器可避免一次性加载大量数据,降低内存占用;
  5. 安全遍历:迭代器可控制遍历过程(如禁止遍历中修改集合),避免并发修改异常。
缺点
  1. 增加代码复杂度:简单集合(如固定大小数组)使用迭代器会增加额外类;
  2. 遍历效率略低:迭代器的封装会带来轻微的性能损耗(可忽略,除非超高频遍历);
  3. 单向遍历为主:大部分迭代器是单向的(只能往后遍历),双向迭代器实现复杂。
适用场景
  1. 遍历集合但不想暴露内部结构:如自定义集合类、框架中的容器;
  2. 统一不同集合的遍历方式:如同时遍历 ArrayList、LinkedList、HashMap;
  3. 分页遍历大数据:如数据库分页查询、接口分批拉取数据、大数据文件读取;
  4. 多线程遍历:每个线程持有独立迭代器,避免遍历状态冲突;
  5. 需要安全遍历:如禁止遍历中修改集合(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 访问者模式(易混淆点)

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

总结

  1. 迭代器模式的核心是将集合的遍历逻辑抽离为独立的迭代器对象,实现遍历与存储的解耦;
  2. 核心角色包括迭代器接口(统一遍历方法)、具体迭代器(封装遍历逻辑)、聚合接口(创建迭代器)、具体聚合(存储元素);
  3. 业务开发中,迭代器模式最实用的场景是分页遍历大数据集合(如数据库分页、分批接口调用),可避免一次性加载大量数据;
  4. JDK 中所有集合类都基于迭代器模式实现,Iterator接口是遍历所有集合的标准方式,foreach 循环底层也是调用迭代器。
相关推荐
Bert.Cai2 小时前
Python type函数详解
开发语言·python
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧2 小时前
Day01 Junit 单元测试 & 反射
java·后端·junit·单元测试
geovindu2 小时前
python: Singleton Pattern
开发语言·python·单例模式·设计模式
JTCC2 小时前
Java 设计模式西游篇 - 第七回:责任链模式过难关 通关文牒层层批
java·设计模式·责任链模式
xiaoye-duck2 小时前
《算法题讲解指南:优选算法-分治-归并》--47.归并排序,48.数组中的逆序对
c++·算法
Darkwanderor2 小时前
图论——最短路问题
c++·算法·图论·最短路
Java练习两年半2 小时前
互联网大厂 Java 求职面试:探讨微服务与云原生
java·微服务·云原生·面试·技术栈
Filotimo_2 小时前
3.4 图
算法·图论
I_LPL2 小时前
day49 代码随想录算法训练营 图论专题2
java·算法·深度优先·图论·广度优先·求职面试