欢迎你来读这篇博客,这篇博客主要是关于迭代器模式。 其中包括迭代器模式的核心思想、适用场景、优缺点、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 ,属于行为型设计模式。
它的定义是:
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
简单说:
迭代器模式就是用一个专门的对象来遍历集合,让客户端不用关心集合内部怎么存储。
迭代器模式通常包含几个角色:
Iterator 抽象迭代器 :定义访问元素的接口,例如 hasNext()、next()。
ConcreteIterator 具体迭代器 :实现具体遍历逻辑。
Aggregate 抽象聚合对象 :定义创建迭代器的方法。
ConcreteAggregate 具体聚合对象 :保存元素,并返回对应迭代器。
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 内部是数组。
它只知道:
这就是迭代器模式最基本的思想。
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();
iterator1 和 iterator2 是两个不同的迭代器。
它们各自维护自己的遍历位置。
1 2 3 4 iterator1.next(); iterator1.next(); iterator2.next();
如果把集合对象自己当成迭代器,就容易产生状态混乱。
所以 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:案例背景:订单分页批处理 下面设计一个订单分页迭代器。
需求如下:
数据库中有大量订单;
不能一次性加载到内存;
每次分页查询一批订单;
客户端像遍历普通集合一样遍历订单;
迭代器内部负责翻页;
支持批处理任务。
为了简化示例,用内存模拟数据库。
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) ; }
这里用 pageNo 和 pageSize 模拟分页查询。
真实项目中可能是:
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 > lastIdORDER 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
迭代器回答:
访问者回答:
两者可以一起使用。
chapter 33:迭代器模式和责任链模式的区别 责任链也是一个一个节点往后走,容易和迭代器混。
对比项
迭代器模式
责任链模式
目的
遍历集合元素
让请求沿链处理
元素角色
被访问的数据
处理请求的对象
控制方式
客户端调用 next
请求自动传递
是否处理请求
不一定
是
示例
遍历订单集合
校验链、过滤器链
迭代器是“取数据”。
责任链是“处理请求”。
不要看到都一个接一个就混了。
chapter 34:迭代器模式和游标 Cursor 数据库游标、Redis Scan、Elasticsearch Scroll 都有迭代器思想。
它们共同点是:
不一次性加载全部数据,而是分批获取下一部分。
例如 Redis Scan:
1 SCAN cursor MATCH pattern COUNT count
每次返回:
这就是典型的游标遍历。
在后端批量任务里,游标式迭代非常重要。
它比普通 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 > lastIdORDER BY id LIMIT 1000
3. 分页迭代器要记录日志 例如每次查询:
1 2 3 4 lastId pageSize resultSize cost
线上排查批处理问题非常有用。
4. 自定义迭代器要处理边界 包括:
空集合;
最后一页不足 pageSize;
hasNext 多次调用;
next 越界;
数据源异常;
资源释放。
5. 注意资源关闭 如果迭代器持有文件流、数据库游标、网络连接,应该实现关闭机制。
例如:
使用 try-with-resources。
6. 不要在迭代器里写复杂业务 迭代器只负责取下一个元素。
业务处理应该放在外部 Processor 中。
7. 批处理最好按批次处理事务 单个元素迭代器适合简单处理。
如果需要事务,批次迭代器更合适。
8. 树遍历要防止环 如果数据不是严格树,可能出现循环引用。
自定义树迭代器时要考虑 visited 集合。
9. 分页查询要保证稳定排序 分页迭代必须有稳定排序字段。
例如:
没有稳定排序,分页结果可能乱。
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 中,Iterator、Iterable、增强 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.
启示录 富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。