设计模式:迭代器模式

欢迎你来读这篇博客,这篇博客主要是关于迭代器模式
其中包括迭代器模式的核心思想、适用场景、优缺点、Java 中的 Iterator 与 Iterable、fail-fast 机制、分页迭代器,以及 Java 后端开发中订单分页批处理的完整案例。

序言

在 Java 开发中,我们每天都在使用迭代器模式,只是很多时候没有意识到。

比如:

1
2
3
4
5
List<String> names = List.of("Mario", "Luigi", "Peach");

for (String name : names) {
System.out.println(name);
}

这段增强 for 循环背后,本质上就和迭代器有关。

再比如:

1
2
3
4
5
6
7
Iterator<String> iterator = names.iterator();

while (iterator.hasNext()) {
String name = iterator.next();

System.out.println(name);
}

这就是非常典型的迭代器模式。

迭代器模式要解决的问题是:

如何在不暴露集合内部结构的情况下,按顺序访问集合中的元素。

为什么这件事重要?

因为不同集合的底层结构完全不同:

  • ArrayList 底层是数组;
  • LinkedList 底层是链表;
  • HashSet 底层是哈希表;
  • TreeSet 底层是红黑树;
  • 数据库分页结果来自远程查询;
  • 文件内容来自磁盘流;
  • MQ 消息来自队列;
  • 树形结构来自递归节点。

如果客户端每次遍历都要知道这些内部结构,代码会非常难维护。

迭代器模式的价值就是:

把“怎么遍历”这件事从集合对象中抽出来,让客户端只关心“有没有下一个”和“取下一个”。

这就像你去图书馆借书,不需要知道书库后面是按 ISBN 排、按分类排,还是馆员心情排。你只需要有一个“下一本”的入口。至于背后怎么找,交给迭代器。

正文

chapter 1:什么是迭代器模式

迭代器模式,英文是 Iterator Pattern,属于行为型设计模式。

它的定义是:

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。

简单说:

迭代器模式就是用一个专门的对象来遍历集合,让客户端不用关心集合内部怎么存储。

迭代器模式通常包含几个角色:

  1. Iterator 抽象迭代器:定义访问元素的接口,例如 hasNext()next()
  2. ConcreteIterator 具体迭代器:实现具体遍历逻辑。
  3. Aggregate 抽象聚合对象:定义创建迭代器的方法。
  4. ConcreteAggregate 具体聚合对象:保存元素,并返回对应迭代器。
  5. Client 客户端:通过迭代器遍历元素。

经典结构如下:

1
2
3
4
5
6
7
8
9
Client


Aggregate
│ iterator()

Iterator
│ hasNext()
│ next()

客户端不直接访问集合内部结构,而是通过迭代器访问元素。

chapter 2:为什么需要迭代器模式

假设我们有一个自定义集合,内部用数组保存元素。

1
2
3
4
5
6
7
8
public class NameCollection {

private final String[] names = {"Mario", "Luigi", "Peach"};

public String[] getNames() {
return names;
}
}

客户端遍历:

1
2
3
4
5
6
7
NameCollection collection = new NameCollection();

String[] names = collection.getNames();

for (int i = 0; i < names.length; i++) {
System.out.println(names[i]);
}

这样写的问题是:

客户端知道了集合内部是数组。

如果以后内部结构改成 List

1
private final List<String> names = List.of("Mario", "Luigi", "Peach");

客户端代码就要改。

如果内部结构改成链表、树、分页查询、远程流,客户端也要跟着改。

这显然不是好设计。

迭代器模式的做法是:

集合对象只暴露迭代器,客户端通过迭代器遍历,不关心底层结构。

例如:

1
2
3
4
5
Iterator<String> iterator = collection.iterator();

while (iterator.hasNext()) {
System.out.println(iterator.next());
}

无论底层是数组、链表、数据库还是文件流,客户端遍历方式都可以保持一致。

chapter 3:迭代器模式解决的核心问题

迭代器模式主要解决三个问题。

1. 封装集合内部结构

客户端不需要知道集合内部是数组、链表还是树。

2. 统一遍历方式

不同集合都可以用类似方式遍历:

1
2
3
while (iterator.hasNext()) {
T item = iterator.next();
}

3. 支持多种遍历策略

同一个集合可以提供不同迭代器。

例如:

  • 正序遍历;
  • 倒序遍历;
  • 按条件过滤遍历;
  • 分页遍历;
  • 深度优先遍历;
  • 广度优先遍历。

迭代器模式把遍历逻辑独立出来,让集合本身更干净。

chapter 4:迭代器模式的基本实现

先写一个最简单的迭代器接口。

1
2
3
4
5
6
public interface MyIterator<T> {

boolean hasNext();

T next();
}

再定义一个聚合接口。

1
2
3
4
public interface MyIterable<T> {

MyIterator<T> iterator();
}

定义一个名字集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class NameCollection implements MyIterable<String> {

private final String[] names = {"Mario", "Luigi", "Peach"};

@Override
public MyIterator<String> iterator() {
return new NameIterator();
}

private class NameIterator implements MyIterator<String> {

private int index = 0;

@Override
public boolean hasNext() {
return index < names.length;
}

@Override
public String next() {
if (!hasNext()) {
throw new IllegalStateException("No more elements");
}

return names[index++];
}
}
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class IteratorDemo {

public static void main(String[] args) {
NameCollection collection = new NameCollection();

MyIterator<String> iterator = collection.iterator();

while (iterator.hasNext()) {
String name = iterator.next();

System.out.println(name);
}
}
}

客户端不知道 NameCollection 内部是数组。

它只知道:

1
2
hasNext()
next()

这就是迭代器模式最基本的思想。

chapter 5:Java 中的 Iterator 接口

Java 已经内置了迭代器接口。

1
2
3
4
5
6
7
8
9
10
public interface Iterator<E> {

boolean hasNext();

E next();

default void remove() {
throw new UnsupportedOperationException("remove");
}
}

常用方法有两个:

1
2
boolean hasNext()
E next()

hasNext() 判断是否还有下一个元素。

next() 返回下一个元素。

例如:

1
2
3
4
5
6
7
8
9
List<String> names = List.of("Mario", "Luigi", "Peach");

Iterator<String> iterator = names.iterator();

while (iterator.hasNext()) {
String name = iterator.next();

System.out.println(name);
}

这里的 List 内部提供了自己的迭代器实现。

客户端只依赖 Iterator 接口。

chapter 6:Java 中的 Iterable 接口

增强 for 循环背后依赖的是 Iterable

Iterable 接口大致如下:

1
2
3
4
public interface Iterable<T> {

Iterator<T> iterator();
}

只要一个类实现了 Iterable,就可以使用增强 for 循环。

例如:

1
2
3
4
5
6
7
8
9
public class NameCollection implements Iterable<String> {

private final List<String> names = List.of("Mario", "Luigi", "Peach");

@Override
public Iterator<String> iterator() {
return names.iterator();
}
}

使用:

1
2
3
4
5
NameCollection collection = new NameCollection();

for (String name : collection) {
System.out.println(name);
}

增强 for 循环本质上会被编译器转换成类似:

1
2
3
4
5
6
7
Iterator<String> iterator = collection.iterator();

while (iterator.hasNext()) {
String name = iterator.next();

System.out.println(name);
}

所以当你写增强 for 时,其实已经在使用迭代器模式。

chapter 7:为什么 Iterator 和 Iterable 要分开

Iterator 表示一次遍历过程。

Iterable 表示这个对象可以被遍历。

这两个概念不一样。

例如:

1
2
3
4
5
List<String> names = List.of("A", "B", "C");

Iterator<String> iterator1 = names.iterator();

Iterator<String> iterator2 = names.iterator();

iterator1iterator2 是两个不同的迭代器。

它们各自维护自己的遍历位置。

1
2
3
4
iterator1.next(); // A
iterator1.next(); // B

iterator2.next(); // A

如果把集合对象自己当成迭代器,就容易产生状态混乱。

所以 Java 设计成:

  • Iterable 负责创建迭代器;
  • Iterator 负责一次具体遍历。

这也是一个很重要的设计思想:

可遍历对象和遍历过程要分离。

chapter 8:迭代器模式的核心优势

迭代器模式最大的优势是:

客户端不依赖集合内部结构。

例如:

1
2
3
for (String name : collection) {
System.out.println(name);
}

客户端不关心:

  • collection 内部是不是数组;
  • 是不是链表;
  • 是不是 Set;
  • 是不是数据库分页;
  • 是不是文件流;
  • 是不是远程接口返回;
  • 是不是树结构遍历结果。

只要它能提供迭代器,客户端就能统一遍历。

这就是迭代器模式的工程价值。

chapter 9:正序迭代器和倒序迭代器

同一个集合可以提供多个迭代器。

例如一个任务列表,既可以正序遍历,也可以倒序遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class TaskCollection implements Iterable<String> {

private final List<String> tasks;

public TaskCollection(List<String> tasks) {
this.tasks = List.copyOf(tasks);
}

@Override
public Iterator<String> iterator() {
return tasks.iterator();
}

public Iterator<String> reverseIterator() {
return new ReverseTaskIterator();
}

private class ReverseTaskIterator implements Iterator<String> {

private int index = tasks.size() - 1;

@Override
public boolean hasNext() {
return index >= 0;
}

@Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}

return tasks.get(index--);
}
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Iterator;
import java.util.List;

public class ReverseIteratorDemo {

public static void main(String[] args) {
TaskCollection tasks = new TaskCollection(
List.of("参数校验", "库存校验", "创建订单", "发送通知")
);

System.out.println("正序遍历:");
for (String task : tasks) {
System.out.println(task);
}

System.out.println("倒序遍历:");
Iterator<String> reverseIterator = tasks.reverseIterator();

while (reverseIterator.hasNext()) {
System.out.println(reverseIterator.next());
}
}
}

这说明迭代器不仅可以隐藏内部结构,还可以封装不同遍历策略。

chapter 10:过滤迭代器

有时候我们只想遍历满足条件的元素。

例如只遍历启用状态的菜单。

先定义菜单对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MenuItem {

private final String name;

private final boolean enabled;

public MenuItem(String name, boolean enabled) {
this.name = name;
this.enabled = enabled;
}

public String getName() {
return name;
}

public boolean isEnabled() {
return enabled;
}
}

定义过滤迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Predicate;

public class FilteringIterator<T> implements Iterator<T> {

private final Iterator<T> delegate;

private final Predicate<T> predicate;

private T nextItem;

private boolean nextReady = false;

public FilteringIterator(Iterator<T> delegate, Predicate<T> predicate) {
this.delegate = delegate;
this.predicate = predicate;
}

@Override
public boolean hasNext() {
if (nextReady) {
return true;
}

while (delegate.hasNext()) {
T item = delegate.next();

if (predicate.test(item)) {
nextItem = item;
nextReady = true;

return true;
}
}

return false;
}

@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}

T result = nextItem;

nextItem = null;
nextReady = false;

return result;
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.Iterator;
import java.util.List;

public class FilteringIteratorDemo {

public static void main(String[] args) {
List<MenuItem> menus = List.of(
new MenuItem("用户管理", true),
new MenuItem("角色管理", true),
new MenuItem("旧版报表", false)
);

Iterator<MenuItem> iterator = new FilteringIterator<>(
menus.iterator(),
MenuItem::isEnabled
);

while (iterator.hasNext()) {
System.out.println(iterator.next().getName());
}
}
}

过滤逻辑被封装在迭代器里。

客户端只负责遍历。

chapter 11:Java 集合中的迭代器模式

Java 集合框架大量使用迭代器模式。

例如:

1
2
3
List<String> list = new ArrayList<>();
Set<String> set = new HashSet<>();
Queue<String> queue = new LinkedList<>();

它们都可以这样遍历:

1
2
3
for (String item : collection) {
System.out.println(item);
}

客户端不需要关心:

  • ArrayList 是数组;
  • LinkedList 是链表;
  • HashSet 是哈希表;
  • TreeSet 是红黑树。

这就是迭代器模式带来的统一遍历体验。

chapter 12:Iterator 的 remove 方法

Java 的 Iterator 有一个 remove() 方法。

它用于删除当前迭代到的元素。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorRemoveDemo {

public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Mario");
names.add("Luigi");
names.add("Peach");

Iterator<String> iterator = names.iterator();

while (iterator.hasNext()) {
String name = iterator.next();

if ("Luigi".equals(name)) {
iterator.remove();
}
}

System.out.println(names);
}
}

不要在增强 for 里直接修改集合:

1
2
3
4
5
for (String name : names) {
if ("Luigi".equals(name)) {
names.remove(name);
}
}

这可能会触发 ConcurrentModificationException

正确方式是使用迭代器的 remove()

chapter 13:fail-fast 机制

Java 集合中有一个常见现象:遍历集合时直接修改集合,可能会抛出:

1
ConcurrentModificationException

例如:

1
2
3
4
5
6
7
8
9
10
List<String> names = new ArrayList<>();
names.add("Mario");
names.add("Luigi");
names.add("Peach");

for (String name : names) {
if ("Luigi".equals(name)) {
names.remove(name);
}
}

这可能抛异常。

这是因为 ArrayList 的迭代器有 fail-fast 机制。

它会记录集合结构修改次数。

如果在迭代过程中发现集合被非迭代器方式修改,就会快速失败。

fail-fast 的目的

fail-fast 不是为了保证线程安全。

它的目的是:

尽早发现并发修改或错误修改,避免产生不可预测结果。

也就是说,它是一个错误检测机制。

正确删除方式

1
2
3
4
5
6
7
8
9
Iterator<String> iterator = names.iterator();

while (iterator.hasNext()) {
String name = iterator.next();

if ("Luigi".equals(name)) {
iterator.remove();
}
}

这才是迭代过程中删除元素的推荐方式。

chapter 14:fail-safe 迭代器

和 fail-fast 相对,还有一种 fail-safe 思路。

例如 CopyOnWriteArrayList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class FailSafeIteratorDemo {

public static void main(String[] args) {
List<String> names = new CopyOnWriteArrayList<>();
names.add("Mario");
names.add("Luigi");
names.add("Peach");

Iterator<String> iterator = names.iterator();

while (iterator.hasNext()) {
String name = iterator.next();

if ("Luigi".equals(name)) {
names.remove(name);
}

System.out.println(name);
}

System.out.println(names);
}
}

CopyOnWriteArrayList 的迭代器遍历的是快照。

遍历过程中修改集合,不会影响当前迭代器。

所以不会抛 ConcurrentModificationException

但是它也有成本:

  • 写操作会复制数组;
  • 不适合写多场景;
  • 适合读多写少场景。

chapter 15:迭代器模式和 Stream 的关系

Java 8 之后,很多遍历逻辑会使用 Stream。

例如:

1
2
3
names.stream()
.filter(name -> name.startsWith("M"))
.forEach(System.out::println);

Stream 和 Iterator 都能遍历数据,但关注点不同。

对比项 Iterator Stream
主要目的 顺序访问元素 声明式数据处理
风格 外部迭代 内部迭代
控制权 客户端控制 next Stream 控制遍历
支持操作 hasNext、next filter、map、reduce
惰性计算 一般手动控制 支持惰性中间操作
并行处理 不直接支持 支持 parallelStream

外部迭代:

1
2
3
4
5
6
7
Iterator<String> iterator = names.iterator();

while (iterator.hasNext()) {
String name = iterator.next();

System.out.println(name);
}

内部迭代:

1
names.forEach(System.out::println);

Stream 并没有让迭代器模式过时。

它只是提供了更高级的数据处理方式。

底层很多地方仍然离不开迭代思想。

chapter 16:后端场景:为什么需要分页迭代器

在后端开发中,迭代器模式不只用于内存集合。

它也很适合数据库分页处理。

假设要处理 1000 万条订单数据。

如果一次性查出来:

1
List<Order> orders = orderRepository.findAll();

那内存可能直接爆掉。

更合理的方式是分页查询:

1
2
3
4
第 1 页查 1000 条
第 2 页查 1000 条
第 3 页查 1000 条
...

但是如果每个业务都手写分页循环,会非常重复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int pageNo = 1;

while (true) {
List<Order> orders = orderRepository.findPage(pageNo, pageSize);

if (orders.isEmpty()) {
break;
}

for (Order order : orders) {
process(order);
}

pageNo++;
}

这时可以把分页逻辑封装成迭代器。

客户端只需要:

1
2
3
4
5
6
7
Iterator<Order> iterator = new OrderPageIterator(orderRepository, 1000);

while (iterator.hasNext()) {
Order order = iterator.next();

process(order);
}

这就是迭代器模式在 Java 后端中的非常实用的场景。

chapter 17:案例背景:订单分页批处理

下面设计一个订单分页迭代器。

需求如下:

  1. 数据库中有大量订单;
  2. 不能一次性加载到内存;
  3. 每次分页查询一批订单;
  4. 客户端像遍历普通集合一样遍历订单;
  5. 迭代器内部负责翻页;
  6. 支持批处理任务。

为了简化示例,用内存模拟数据库。

chapter 18:定义订单对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.math.BigDecimal;

public class Order {

private final Long orderId;

private final Long userId;

private final BigDecimal amount;

private final String status;

public Order(Long orderId, Long userId, BigDecimal amount, String status) {
this.orderId = orderId;
this.userId = userId;
this.amount = amount;
this.status = status;
}

public Long getOrderId() {
return orderId;
}

public Long getUserId() {
return userId;
}

public BigDecimal getAmount() {
return amount;
}

public String getStatus() {
return status;
}
}

chapter 19:定义订单仓储接口

1
2
3
4
5
6
import java.util.List;

public interface OrderRepository {

List<Order> findPage(int pageNo, int pageSize);
}

这里用 pageNopageSize 模拟分页查询。

真实项目中可能是:

  • MyBatis 分页;
  • JPA PageRequest;
  • 游标分页;
  • Elasticsearch scroll;
  • MongoDB cursor;
  • Redis scan。

chapter 20:定义内存订单仓储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class InMemoryOrderRepository implements OrderRepository {

private final List<Order> orders = new ArrayList<>();

public InMemoryOrderRepository(int total) {
for (long i = 1; i <= total; i++) {
orders.add(new Order(
i,
1000L + i,
new BigDecimal("99.00"),
i % 2 == 0 ? "PAID" : "CREATED"
));
}
}

@Override
public List<Order> findPage(int pageNo, int pageSize) {
int fromIndex = (pageNo - 1) * pageSize;

if (fromIndex >= orders.size()) {
return List.of();
}

int toIndex = Math.min(fromIndex + pageSize, orders.size());

System.out.println("查询订单分页,pageNo = " + pageNo
+ ",pageSize = " + pageSize
+ ",fromIndex = " + fromIndex
+ ",toIndex = " + toIndex);

return orders.subList(fromIndex, toIndex);
}
}

chapter 21:实现订单分页迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class OrderPageIterator implements Iterator<Order> {

private final OrderRepository orderRepository;

private final int pageSize;

private int currentPageNo = 1;

private List<Order> currentPage = List.of();

private int currentIndex = 0;

private boolean loaded = false;

private boolean finished = false;

public OrderPageIterator(OrderRepository orderRepository, int pageSize) {
if (orderRepository == null) {
throw new IllegalArgumentException("orderRepository can not be null");
}

if (pageSize <= 0) {
throw new IllegalArgumentException("pageSize must be greater than 0");
}

this.orderRepository = orderRepository;
this.pageSize = pageSize;
}

@Override
public boolean hasNext() {
if (finished) {
return false;
}

if (!loaded || currentIndex >= currentPage.size()) {
loadNextPage();
}

return !finished;
}

@Override
public Order next() {
if (!hasNext()) {
throw new NoSuchElementException();
}

return currentPage.get(currentIndex++);
}

private void loadNextPage() {
currentPage = orderRepository.findPage(currentPageNo, pageSize);
currentPageNo++;
currentIndex = 0;
loaded = true;

if (currentPage.isEmpty()) {
finished = true;
}
}
}

这个迭代器内部维护了:

  • 当前页码;
  • 当前页数据;
  • 当前页索引;
  • 是否加载过;
  • 是否已经结束。

客户端不需要关心分页细节。

chapter 22:使用订单分页迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Iterator;

public class OrderPageIteratorDemo {

public static void main(String[] args) {
OrderRepository orderRepository = new InMemoryOrderRepository(25);

Iterator<Order> iterator = new OrderPageIterator(orderRepository, 10);

int count = 0;

while (iterator.hasNext()) {
Order order = iterator.next();

System.out.println("处理订单,orderId = " + order.getOrderId()
+ ",status = " + order.getStatus());

count++;
}

System.out.println("处理订单总数:" + count);
}
}

输出类似:

1
2
3
4
5
6
7
8
查询订单分页,pageNo = 1,pageSize = 10,fromIndex = 0,toIndex = 10
处理订单,orderId = 1,status = CREATED
...
查询订单分页,pageNo = 2,pageSize = 10,fromIndex = 10,toIndex = 20
...
查询订单分页,pageNo = 3,pageSize = 10,fromIndex = 20,toIndex = 25
...
处理订单总数:25

客户端只是普通遍历。

翻页逻辑被封装在迭代器里。

这就是迭代器模式在后端批处理中的价值。

chapter 23:分页迭代器的风险

分页迭代器很实用,但也有风险。

1. 普通 pageNo 分页可能漏数据或重复数据

如果处理过程中数据发生变化,比如新增、删除、排序字段变化,就可能导致:

  • 某些数据重复处理;
  • 某些数据被跳过。

2. 深分页性能差

数据库中:

1
LIMIT 1000 OFFSET 1000000

可能性能很差。

3. 推荐使用游标分页

更好的方式是基于 ID 游标:

1
2
3
WHERE id > lastId
ORDER BY id ASC
LIMIT 1000

这种方式更适合大数据量批处理。

下面继续写一个游标迭代器。

chapter 24:游标分页仓储接口

1
2
3
4
5
6
import java.util.List;

public interface CursorOrderRepository {

List<Order> findAfterId(Long lastOrderId, int pageSize);
}

chapter 25:内存游标仓储实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class InMemoryCursorOrderRepository implements CursorOrderRepository {

private final List<Order> orders = new ArrayList<>();

public InMemoryCursorOrderRepository(int total) {
for (long i = 1; i <= total; i++) {
orders.add(new Order(
i,
1000L + i,
new BigDecimal("99.00"),
i % 2 == 0 ? "PAID" : "CREATED"
));
}
}

@Override
public List<Order> findAfterId(Long lastOrderId, int pageSize) {
long cursor = lastOrderId == null ? 0L : lastOrderId;

List<Order> result = orders.stream()
.filter(order -> order.getOrderId() > cursor)
.limit(pageSize)
.toList();

System.out.println("游标查询订单,lastOrderId = " + lastOrderId
+ ",pageSize = " + pageSize
+ ",resultSize = " + result.size());

return result;
}
}

chapter 26:游标分页迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class CursorOrderIterator implements Iterator<Order> {

private final CursorOrderRepository repository;

private final int pageSize;

private Long lastOrderId = null;

private List<Order> currentBatch = List.of();

private int currentIndex = 0;

private boolean finished = false;

public CursorOrderIterator(CursorOrderRepository repository, int pageSize) {
if (repository == null) {
throw new IllegalArgumentException("repository can not be null");
}

if (pageSize <= 0) {
throw new IllegalArgumentException("pageSize must be greater than 0");
}

this.repository = repository;
this.pageSize = pageSize;
}

@Override
public boolean hasNext() {
if (finished) {
return false;
}

if (currentIndex >= currentBatch.size()) {
loadNextBatch();
}

return !finished;
}

@Override
public Order next() {
if (!hasNext()) {
throw new NoSuchElementException();
}

Order order = currentBatch.get(currentIndex++);

lastOrderId = order.getOrderId();

return order;
}

private void loadNextBatch() {
currentBatch = repository.findAfterId(lastOrderId, pageSize);
currentIndex = 0;

if (currentBatch.isEmpty()) {
finished = true;
}
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.Iterator;

public class CursorIteratorDemo {

public static void main(String[] args) {
CursorOrderRepository repository = new InMemoryCursorOrderRepository(25);

Iterator<Order> iterator = new CursorOrderIterator(repository, 10);

int count = 0;

while (iterator.hasNext()) {
Order order = iterator.next();

System.out.println("处理订单,orderId = " + order.getOrderId());

count++;
}

System.out.println("处理订单总数:" + count);
}
}

游标分页更适合大数据量顺序扫描。

chapter 27:Spring Boot 中落地分页迭代器

在 Spring Boot 中,分页迭代器可以用于批处理任务。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import org.springframework.stereotype.Component;

import java.util.Iterator;

@Component
public class OrderBatchProcessor {

private final CursorOrderRepository orderRepository;

public OrderBatchProcessor(CursorOrderRepository orderRepository) {
this.orderRepository = orderRepository;
}

public void processPaidOrders() {
Iterator<Order> iterator = new CursorOrderIterator(orderRepository, 1000);

while (iterator.hasNext()) {
Order order = iterator.next();

process(order);
}
}

private void process(Order order) {
System.out.println("批处理订单,orderId = " + order.getOrderId());
}
}

真实项目中可以结合:

  • @Scheduled;
  • XXL-JOB;
  • Spring Batch;
  • Quartz;
  • MQ;
  • 游标分页;
  • 批量提交;
  • 事务分批处理。

例如定时任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class OrderBatchJob {

private final OrderBatchProcessor orderBatchProcessor;

public OrderBatchJob(OrderBatchProcessor orderBatchProcessor) {
this.orderBatchProcessor = orderBatchProcessor;
}

@Scheduled(cron = "0 0 2 * * ?")
public void run() {
orderBatchProcessor.processPaidOrders();
}
}

这样批处理逻辑可以非常清晰:

外部看起来是在遍历订单,内部实际是分页查询。

chapter 28:数据库分页迭代器的事务问题

批量处理数据库数据时,不建议把整个迭代过程放在一个大事务里。

错误方式:

1
2
3
4
5
6
7
8
@Transactional
public void processAllOrders() {
Iterator<Order> iterator = new CursorOrderIterator(repository, 1000);

while (iterator.hasNext()) {
process(iterator.next());
}
}

如果有 100 万条数据,一个事务跑很久,会带来:

  • 锁时间长;
  • 回滚成本高;
  • 数据库压力大;
  • 事务日志膨胀;
  • 失败后全部回滚;
  • 连接长时间占用。

更推荐分批事务。

例如每批查询 1000 条,每批单独处理和提交。

这种场景下,迭代器可以返回批次,而不是单个元素。

chapter 29:批次迭代器

定义批次迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class OrderBatchIterator implements Iterator<List<Order>> {

private final CursorOrderRepository repository;

private final int batchSize;

private Long lastOrderId = null;

private List<Order> nextBatch;

private boolean finished = false;

public OrderBatchIterator(CursorOrderRepository repository, int batchSize) {
this.repository = repository;
this.batchSize = batchSize;
}

@Override
public boolean hasNext() {
if (finished) {
return false;
}

if (nextBatch == null) {
nextBatch = repository.findAfterId(lastOrderId, batchSize);

if (nextBatch.isEmpty()) {
finished = true;
}
}

return !finished;
}

@Override
public List<Order> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}

List<Order> result = nextBatch;

lastOrderId = result.get(result.size() - 1).getOrderId();

nextBatch = null;

return result;
}
}

使用:

1
2
3
4
5
6
7
Iterator<List<Order>> batchIterator = new OrderBatchIterator(repository, 1000);

while (batchIterator.hasNext()) {
List<Order> batch = batchIterator.next();

processBatch(batch);
}

这种批次迭代器在后端任务中非常实用。

chapter 30:迭代器模式和组合模式的关系

树形结构也可以使用迭代器。

例如菜单树:

1
2
3
系统管理
├── 用户管理
└── 角色管理

组合模式负责表达树。

迭代器模式可以负责遍历树。

例如:

  • 深度优先迭代器;
  • 广度优先迭代器。

组合模式回答:

节点之间怎么组成树?

迭代器模式回答:

这棵树怎么遍历?

二者可以组合使用。

chapter 31:深度优先迭代器

假设有树节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.ArrayList;
import java.util.List;

public class TreeNode {

private final String name;

private final List<TreeNode> children = new ArrayList<>();

public TreeNode(String name) {
this.name = name;
}

public void addChild(TreeNode child) {
children.add(child);
}

public String getName() {
return name;
}

public List<TreeNode> getChildren() {
return List.copyOf(children);
}
}

深度优先迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class DepthFirstTreeIterator implements Iterator<TreeNode> {

private final Deque<TreeNode> stack = new ArrayDeque<>();

public DepthFirstTreeIterator(TreeNode root) {
if (root != null) {
stack.push(root);
}
}

@Override
public boolean hasNext() {
return !stack.isEmpty();
}

@Override
public TreeNode next() {
if (!hasNext()) {
throw new NoSuchElementException();
}

TreeNode current = stack.pop();

List<TreeNode> children = current.getChildren();

for (int i = children.size() - 1; i >= 0; i--) {
stack.push(children.get(i));
}

return current;
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TreeIteratorDemo {

public static void main(String[] args) {
TreeNode root = new TreeNode("系统管理");

TreeNode user = new TreeNode("用户管理");
user.addChild(new TreeNode("新增用户"));
user.addChild(new TreeNode("删除用户"));

TreeNode role = new TreeNode("角色管理");
role.addChild(new TreeNode("分配权限"));

root.addChild(user);
root.addChild(role);

Iterator<TreeNode> iterator = new DepthFirstTreeIterator(root);

while (iterator.hasNext()) {
System.out.println(iterator.next().getName());
}
}
}

树的遍历顺序被封装到迭代器里。

客户端不需要写递归。

chapter 32:迭代器模式和访问者模式的区别

迭代器模式和访问者模式都可能用于遍历对象结构,但关注点不同。

对比项 迭代器模式 访问者模式
核心目的 顺序访问元素 在不改元素类的情况下增加操作
关注点 怎么遍历 遍历后做什么操作
典型方法 hasNext、next visit
是否关心元素类型 一般不关心 经常关心不同类型
示例 遍历订单列表 对不同 AST 节点生成 SQL

迭代器回答:

1
下一个元素是谁?

访问者回答:

1
遇到这个元素要执行什么操作?

两者可以一起使用。

chapter 33:迭代器模式和责任链模式的区别

责任链也是一个一个节点往后走,容易和迭代器混。

对比项 迭代器模式 责任链模式
目的 遍历集合元素 让请求沿链处理
元素角色 被访问的数据 处理请求的对象
控制方式 客户端调用 next 请求自动传递
是否处理请求 不一定
示例 遍历订单集合 校验链、过滤器链

迭代器是“取数据”。

责任链是“处理请求”。

不要看到都一个接一个就混了。

chapter 34:迭代器模式和游标 Cursor

数据库游标、Redis Scan、Elasticsearch Scroll 都有迭代器思想。

它们共同点是:

不一次性加载全部数据,而是分批获取下一部分。

例如 Redis Scan:

1
SCAN cursor MATCH pattern COUNT count

每次返回:

  • 下一次 cursor;
  • 当前批次数据。

这就是典型的游标遍历。

在后端批量任务里,游标式迭代非常重要。

它比普通 pageNo 分页更适合大数据量顺序扫描。

chapter 35:迭代器模式的优点

1. 隐藏集合内部结构

客户端不需要知道底层是数组、链表、树还是分页查询。

2. 统一遍历方式

不同集合可以使用相同方式遍历。

1
2
3
while (iterator.hasNext()) {
iterator.next();
}

3. 支持多种遍历策略

同一个集合可以提供正序、倒序、过滤、深度优先、广度优先等不同迭代器。

4. 降低集合类职责

集合负责存储元素。

迭代器负责遍历元素。

职责更清晰。

5. 支持懒加载

迭代器可以在需要时才加载下一批数据。

例如分页迭代器、文件流迭代器。

6. 适合大数据量处理

分页迭代器、游标迭代器可以避免一次性加载大量数据。

chapter 36:迭代器模式的缺点

1. 增加类数量

自定义集合和复杂遍历策略需要额外迭代器类。

2. 简单场景可能没必要

普通 List 直接增强 for 就够了。

3. 迭代时修改集合要谨慎

可能出现 ConcurrentModificationException

4. 迭代器状态容易出错

自定义迭代器要维护当前位置、是否结束、当前批次等状态。

边界条件要写好。

5. 分页迭代器可能隐藏性能问题

客户端看起来是在普通遍历,实际每隔一段就查一次数据库。

要做好日志和监控。

chapter 37:适用场景

迭代器模式适合以下场景。

1. 需要遍历聚合对象

例如集合、树、队列、文件、分页数据。

2. 不希望暴露内部结构

客户端不应该知道底层存储方式。

3. 需要多种遍历方式

例如正序、倒序、过滤、树遍历。

4. 数据量很大,需要懒加载

例如数据库分页、文件流、远程接口分页。

5. 希望统一遍历接口

让不同数据源都可以用统一方式遍历。

chapter 38:不适合使用的场景

以下场景不建议过度使用迭代器模式。

1. 只是简单集合遍历

直接增强 for 即可。

2. 业务逻辑比遍历逻辑更重要

如果核心是业务处理,不要为了迭代器把代码拆得太绕。

3. 数据源不稳定

如果分页过程中数据大量变化,普通分页迭代可能不可靠。

4. 需要随机访问

如果业务频繁按索引访问元素,迭代器不一定合适。

5. 遍历过程需要复杂事务控制

批处理要考虑事务边界,不能只靠迭代器解决。

chapter 39:真实项目中的实践建议

1. 普通集合优先使用 Java 自带 Iterator

不要重复造简单轮子。

1
2
for (Order order : orders) {
}

足够好。

2. 大数据量批处理使用分页或游标迭代器

不要一次性加载全量数据。

推荐游标分页:

1
2
3
WHERE id > lastId
ORDER BY id
LIMIT 1000

3. 分页迭代器要记录日志

例如每次查询:

1
2
3
4
lastId
pageSize
resultSize
cost

线上排查批处理问题非常有用。

4. 自定义迭代器要处理边界

包括:

  • 空集合;
  • 最后一页不足 pageSize;
  • hasNext 多次调用;
  • next 越界;
  • 数据源异常;
  • 资源释放。

5. 注意资源关闭

如果迭代器持有文件流、数据库游标、网络连接,应该实现关闭机制。

例如:

1
AutoCloseable

使用 try-with-resources。

6. 不要在迭代器里写复杂业务

迭代器只负责取下一个元素。

业务处理应该放在外部 Processor 中。

7. 批处理最好按批次处理事务

单个元素迭代器适合简单处理。

如果需要事务,批次迭代器更合适。

8. 树遍历要防止环

如果数据不是严格树,可能出现循环引用。

自定义树迭代器时要考虑 visited 集合。

9. 分页查询要保证稳定排序

分页迭代必须有稳定排序字段。

例如:

1
ORDER BY id ASC

没有稳定排序,分页结果可能乱。

chapter 40:完整案例代码汇总

订单对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.math.BigDecimal;

public class Order {

private final Long orderId;

private final Long userId;

private final BigDecimal amount;

private final String status;

public Order(Long orderId, Long userId, BigDecimal amount, String status) {
this.orderId = orderId;
this.userId = userId;
this.amount = amount;
this.status = status;
}

public Long getOrderId() {
return orderId;
}

public Long getUserId() {
return userId;
}

public BigDecimal getAmount() {
return amount;
}

public String getStatus() {
return status;
}
}

游标仓储接口

1
2
3
4
5
6
import java.util.List;

public interface CursorOrderRepository {

List<Order> findAfterId(Long lastOrderId, int pageSize);
}

内存仓储实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class InMemoryCursorOrderRepository implements CursorOrderRepository {

private final List<Order> orders = new ArrayList<>();

public InMemoryCursorOrderRepository(int total) {
for (long i = 1; i <= total; i++) {
orders.add(new Order(
i,
1000L + i,
new BigDecimal("99.00"),
i % 2 == 0 ? "PAID" : "CREATED"
));
}
}

@Override
public List<Order> findAfterId(Long lastOrderId, int pageSize) {
long cursor = lastOrderId == null ? 0L : lastOrderId;

List<Order> result = orders.stream()
.filter(order -> order.getOrderId() > cursor)
.limit(pageSize)
.toList();

System.out.println("游标查询订单,lastOrderId = " + lastOrderId
+ ",pageSize = " + pageSize
+ ",resultSize = " + result.size());

return result;
}
}

游标订单迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class CursorOrderIterator implements Iterator<Order> {

private final CursorOrderRepository repository;

private final int pageSize;

private Long lastOrderId = null;

private List<Order> currentBatch = List.of();

private int currentIndex = 0;

private boolean finished = false;

public CursorOrderIterator(CursorOrderRepository repository, int pageSize) {
if (repository == null) {
throw new IllegalArgumentException("repository can not be null");
}

if (pageSize <= 0) {
throw new IllegalArgumentException("pageSize must be greater than 0");
}

this.repository = repository;
this.pageSize = pageSize;
}

@Override
public boolean hasNext() {
if (finished) {
return false;
}

if (currentIndex >= currentBatch.size()) {
loadNextBatch();
}

return !finished;
}

@Override
public Order next() {
if (!hasNext()) {
throw new NoSuchElementException();
}

Order order = currentBatch.get(currentIndex++);

lastOrderId = order.getOrderId();

return order;
}

private void loadNextBatch() {
currentBatch = repository.findAfterId(lastOrderId, pageSize);
currentIndex = 0;

if (currentBatch.isEmpty()) {
finished = true;
}
}
}

批处理客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.Iterator;

public class CursorIteratorDemo {

public static void main(String[] args) {
CursorOrderRepository repository = new InMemoryCursorOrderRepository(25);

Iterator<Order> iterator = new CursorOrderIterator(repository, 10);

int count = 0;

while (iterator.hasNext()) {
Order order = iterator.next();

System.out.println("处理订单,orderId = " + order.getOrderId());

count++;
}

System.out.println("处理订单总数:" + count);
}
}

chapter 41:一句话总结

迭代器模式的本质是:

提供一种统一方式顺序访问聚合对象中的元素,而不暴露聚合对象的内部结构。

它特别适合:

  • 集合遍历;
  • 树结构遍历;
  • 文件流读取;
  • 数据库分页扫描;
  • 游标分页处理;
  • 批量任务处理;
  • 自定义聚合对象遍历。

在 Java 中,IteratorIterable、增强 for 循环、集合框架都体现了迭代器模式。

在后端开发中,迭代器模式不仅是遍历 List,更重要的是可以封装分页、游标、批量处理、懒加载等复杂遍历逻辑。

好的迭代器像一个可靠的向导:你不用知道地图有多复杂,它只负责告诉你下一步去哪。

坏的迭代器像一个迷路的导游:hasNext() 说还有,next() 直接摔沟里。

所以写自定义迭代器时,边界条件一定要稳。

参考资料

  • Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software.
  • Joshua Bloch. Effective Java.
  • Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices.
  • Oracle Java Documentation: Iterator.
  • Oracle Java Documentation: Iterable.
  • Oracle Java Documentation: Collections Framework.
  • Spring Batch Documentation.
  • Refactoring Guru: Iterator Pattern.
  • SourceMaking: Iterator Design Pattern.

启示录

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


设计模式:迭代器模式
https://allendericdalexander.github.io/2026/04/01/java/design/17iterator-pattern-blog/
作者
AtLuoFu
发布于
2026年4月1日
许可协议