迭代器模式(Iterator Pattern)
一句话记忆口诀:迭代器统一遍历接口,hasNext + next 是核心,for-each 语法糖底层就是迭代器。
1. 🏠 生活类比
电视遥控器的"下一个频道"按钮:无论电视内部是用数组、链表还是哈希表存储频道列表,用户只需要按"下一个"就能遍历所有频道。用户不需要知道频道是怎么存储的。
图书馆的索引卡片:无论书架上的书怎么排列,索引卡片提供了一种统一的方式来逐本查找。
2. 💩 烂代码:遍历方式与集合类型强耦合
java
// ❌ 反例:每种集合都要用不同的遍历方式
// 数组遍历
String[] array = {"a", "b", "c"};
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
// 链表遍历
LinkedList<String> list = new LinkedList<>();
for (Node node = list.head; node != null; node = node.next) {
System.out.println(node.value);
}
// 二叉树遍历(中序)
void inorder(TreeNode node) {
if (node == null) return;
inorder(node.left);
System.out.println(node.value);
inorder(node.right);
}
// 如果集合类型从数组换成链表,遍历代码全部要改!
// 客户端必须了解每种集合的内部结构!问题根因:不同集合有不同的内部结构和遍历方式,客户端与集合的具体实现强耦合。
3. ✨ 迭代器模式方案
4. 💻 完整代码实现
java
import java.util.*;
import java.util.stream.StreamSupport;
// ===== 自定义集合:书架 =====
public class BookShelf implements Iterable<Book> {
private final List<Book> books = new ArrayList<>();
public void addBook(Book book) {
books.add(book);
}
public Book getBookAt(int index) {
return books.get(index);
}
public int size() {
return books.size();
}
@Override
public Iterator<Book> iterator() {
return new BookIterator(this);
}
}
// ===== 书籍 =====
public class Book {
private final String name;
private final String author;
public Book(String name, String author) {
this.name = name;
this.author = author;
}
public String getName() { return name; }
public String getAuthor() { return author; }
@Override
public String toString() {
return "《" + name + "》- " + author;
}
}
// ===== 具体迭代器 =====
public class BookIterator implements Iterator<Book> {
private final BookShelf shelf;
private int index = 0;
public BookIterator(BookShelf shelf) {
this.shelf = shelf;
}
@Override
public boolean hasNext() {
return index < shelf.size();
}
@Override
public Book next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Book book = shelf.getBookAt(index);
index++;
return book;
}
}
// ===== 使用示例 =====
public class Main {
public static void main(String[] args) {
BookShelf shelf = new BookShelf();
shelf.addBook(new Book("设计模式", "GoF"));
shelf.addBook(new Book("重构", "Martin Fowler"));
shelf.addBook(new Book("Effective Java", "Joshua Bloch"));
// 方式一:显式使用迭代器
Iterator<Book> it = shelf.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
// 方式二:for-each 语法糖(底层就是迭代器)
for (Book book : shelf) {
System.out.println(book);
}
// 方式三:Stream(内部迭代,Iterable 需经 StreamSupport 转流)
StreamSupport.stream(shelf.spliterator(), false)
.filter(b -> b.getName().contains("设计"))
.forEach(System.out::println);
}
}4.1 进阶示例:二叉树支持多种遍历方式(中序实现示例)
java
// ===== 二叉树支持前序、中序、后序遍历 =====
public class BinaryTree<T> implements Iterable<T> {
private TreeNode<T> root;
private TraversalOrder order = TraversalOrder.INORDER;
public enum TraversalOrder { PREORDER, INORDER, POSTORDER }
public void setOrder(TraversalOrder order) { this.order = order; }
@Override
public Iterator<T> iterator() {
return switch (order) {
case PREORDER -> new PreorderIterator<>(root);
case INORDER -> new InorderIterator<>(root);
case POSTORDER -> new PostorderIterator<>(root);
};
}
// PreorderIterator 和 PostorderIterator 的实现从略,此处仅展示不同遍历顺序对应的迭代器选择。
// 中序迭代器实现
private static class InorderIterator<T> implements Iterator<T> {
private final Stack<TreeNode<T>> stack = new Stack<>();
public InorderIterator(TreeNode<T> root) {
pushLeft(root);
}
private void pushLeft(TreeNode<T> node) {
while (node != null) {
stack.push(node);
node = node.left;
}
}
@Override
public boolean hasNext() { return !stack.isEmpty(); }
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
TreeNode<T> node = stack.pop();
pushLeft(node.right);
return node.value;
}
}
}
class TreeNode<T> {
T value;
TreeNode<T> left, right;
TreeNode(T value) { this.value = value; }
}5. 🔧 框架应用
| 框架/类 | 说明 |
|---|---|
java.util.Iterator | JDK 标准迭代器接口 |
Iterable + for-each | 语法糖,编译后使用 Iterator |
java.util.stream.Stream | Java 8 内部迭代器,支持惰性求值 |
ListIterator | 双向迭代器,支持向前/向后遍历 |
Enumeration | JDK 1.0 的迭代器(已被 Iterator 替代) |
MyBatis Cursor | 大数据量查询的流式迭代器 |
ResultSet | JDBC 结果集遍历 |
6. ⚠️ 适用场景
适合:
- 需要统一遍历不同类型的集合
- 不想暴露集合的内部结构
- 需要多种遍历方式(前序/中序/后序)
- 需要惰性遍历(迭代器按 next 逐个取元素,可配合 MyBatis Cursor 流式读取大数据量)
不适合:
- 集合结构简单,直接 for 循环即可
- 需要按索引频繁随机访问(Iterator 接口没有随机访问方法,单靠 Iterator 不合适)
7. 🔍 迭代器的 fail-fast vs fail-safe
| 类型 | 代表 | 特点 |
|---|---|---|
| fail-fast | ArrayList.iterator() | 遍历中修改集合 → 可能抛 ConcurrentModificationException |
| fail-safe | CopyOnWriteArrayList.iterator() | 遍历中修改集合 → 不抛异常(遍历的是快照) |
说明:fail-safe 是常见非正式称呼,并非 Java 集合框架的正式分类。ArrayList 迭代器通过内部修改次数检查「尽力检测」结构性修改,但不保证一定抛出异常;上面的异常发生在同一线程的遍历过程中,说明 fail-fast 并不只针对并发场景。CopyOnWriteArrayList 的迭代器遍历的是创建迭代器时的快照,因此看不到之后的更新,代价是每次写操作都要复制整个数组,写多读少时开销较大。
java
// fail-fast 示例
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
if ("a".equals(s)) {
list.remove(s); // 当前 ArrayList 实现会在后续迭代时抛出 CME
}
}
// 正确做法:使用迭代器的 remove 方法
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("b".equals(it.next())) it.remove(); // ✅ 安全删除
}