深入解析Java迭代器模式:从理论到实战案例的完整指南
目录导读
- 什么是迭代器模式? —— 核心定义与设计初衷
- 为什么需要迭代器模式? —— 解决集合遍历的耦合问题
- Java中的迭代器模式实现 —— Iterator接口与具体实现
- 经典案例1:自定义集合类的迭代器实现
- 经典案例2:树形结构的中序遍历迭代器
- 经典案例3:数据库结果集的游标式迭代
- 迭代器模式与增强for循环的关系
- 常见问题QA —— 开发中高频疑问解答
- 迭代器模式在JDK源码中的应用
- 最佳实践与性能优化建议
什么是迭代器模式?
迭代器模式(Iterator Pattern) 是一种行为型设计模式,它提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。

迭代器模式将“遍历”这个行为从集合对象中抽离出来,封装成一个独立的迭代器对象,这样,无论底层集合是数组、链表、树还是图,调用者都可以用统一的方式访问元素。
核心角色
| 角色 | 说明 | Java示例 |
|---|---|---|
| Iterator(迭代器接口) | 定义hasNext()、next()等方法 | java.util.Iterator |
| ConcreteIterator(具体迭代器) | 实现迭代逻辑,维护遍历位置 | ArrayList的内部Itr类 |
| Aggregate(聚合接口) | 定义创建迭代器的方法 | java.util.Collection |
| ConcreteAggregate(具体聚合) | 返回一个具体的迭代器实例 | ArrayList、HashSet |
为什么需要迭代器模式?
假设你有一个列表,需要遍历所有元素,传统做法是直接使用for循环下标访问:
for (int i = 0; i < list.size(); i++) {
Item item = list.get(i);
// 处理item
}
但如果你把列表换成树或Set呢?代码必须重写,迭代器模式的核心价值在于:
- 解耦遍历算法与集合结构 —— 集合改变时,遍历代码无需改动
- 统一遍历接口 —— 无论什么集合,都是hasNext()+next()
- 支持多种遍历方式 —— 同一个集合可以有正序、逆序、条件过滤等多种迭代器
- 延迟遍历 —— 按需访问,不需要一次性加载所有元素(对大数据量特别重要)
Java中的迭代器模式实现
Java从1.2版本开始便内置了迭代器模式,核心在 java.util.Iterator 接口中:
public interface Iterator<E> {
boolean hasNext(); // 是否还有下一个元素
E next(); // 返回下一个元素
default void remove() { throw new UnsupportedOperationException(); }
// Java 8 新增的forEachRemaining
default void forEachRemaining(Consumer<? super E> action) { ... }
}
所有集合类(ArrayList、HashSet、LinkedList等)都实现了 Iterable 接口,该接口返回一个Iterator实例:
public interface Iterable<T> {
Iterator<T> iterator();
}
这意味着任何集合都可以通过 for (T item : collection) 语法遍历——这正是迭代器模式在Java中最广泛的应用。
经典案例1:自定义集合类的迭代器实现
假设我们有一个 BookShelf(书架)类,内部用数组存放书籍,但我们希望客户端能像遍历ArrayList一样遍历它。
步骤1:定义聚合接口
public interface Aggregate {
Iterator<Book> iterator();
}
步骤2:定义书籍类
public class Book {
private String name;
public Book(String name) { this.name = name; }
public String getName() { return name; }
}
步骤3:实现具体聚合类
public class BookShelf implements Aggregate {
private Book[] books;
private int last = 0;
public BookShelf(int maxSize) {
this.books = new Book[maxSize];
}
public void appendBook(Book book) {
this.books[last] = book;
last++;
}
public int getLength() { return last; }
@Override
public Iterator<Book> iterator() {
return new BookShelfIterator(this);
}
}
步骤4:实现具体迭代器
public class BookShelfIterator implements Iterator<Book> {
private BookShelf bookShelf;
private int index;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < bookShelf.getLength();
}
@Override
public Book next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return bookShelf.getBookAt(index++);
}
}
步骤5:使用迭代器
public class Main {
public static void main(String[] args) {
BookShelf shelf = new BookShelf(5);
shelf.appendBook(new Book("Design Patterns"));
shelf.appendBook(new Book("Clean Code"));
shelf.appendBook(new Book("Effective Java"));
// 使用迭代器遍历
Iterator<Book> it = shelf.iterator();
while (it.hasNext()) {
Book book = it.next();
System.out.println(book.getName());
}
// 也支持增强for循环
for (Book book : shelf) {
System.out.println(book.getName());
}
}
}
关键点:客户端完全不知道 BookShelf 内部是用数组存储的,如果后续改成 ArrayList 或 LinkedList,只要迭代器实现正确,客户端代码无需任何修改。
经典案例2:树形结构的中序遍历迭代器
迭代器模式最强大的应用之一是对非线性结构的遍历,例如二叉树的中序遍历,我们可以实现一个 InOrderIterator。
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public class InOrderIterator implements Iterator<Integer> {
private Stack<TreeNode> stack = new Stack<>();
private TreeNode current;
public InOrderIterator(TreeNode root) {
this.current = root;
// 先一路向左压栈
while (current != null) {
stack.push(current);
current = current.left;
}
}
@Override
public boolean hasNext() {
return !stack.isEmpty();
}
@Override
public Integer next() {
if (!hasNext()) throw new NoSuchElementException();
TreeNode node = stack.pop();
int result = node.val;
// 如果有右子树,压入右子树及其整条左链
if (node.right != null) {
current = node.right;
while (current != null) {
stack.push(current);
current = current.left;
}
}
return result;
}
}
使用场景:在处理XML/JSON解析、文件系统遍历、游戏场景管理时,这种基于状态的迭代器非常有价值。
经典案例3:数据库结果集的游标式迭代
迭代器模式也常用于数据库访问层,例如模拟一个简单的结果集游标:
public class ResultSetIterator implements Iterator<Map<String, Object>> {
private final ResultSet resultSet;
private boolean hasNext = false;
private boolean moved = false;
public ResultSetIterator(ResultSet rs) {
this.resultSet = rs;
}
@Override
public boolean hasNext() {
if (!moved) {
try {
hasNext = resultSet.next();
moved = true;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return hasNext;
}
@Override
public Map<String, Object> next() {
if (!hasNext()) throw new NoSuchElementException();
moved = false;
// 将当前行转换为Map
Map<String, Object> row = new HashMap<>();
try {
ResultSetMetaData meta = resultSet.getMetaData();
for (int i = 1; i <= meta.getColumnCount(); i++) {
row.put(meta.getColumnName(i), resultSet.getObject(i));
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return row;
}
}
这样我们便可以用统一的方式处理数据库记录,甚至可以和Stream API结合:
ResultSetIterator it = new ResultSetIterator(rs);
StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, 0), false)
.filter(row -> "Active".equals(row.get("status")))
.forEach(row -> System.out.println(row));
优势:数据库游标天然就是一个迭代器设计,逐条获取、按需加载,而不是一次性将所有数据加载到内存。
迭代器模式与增强for循环的关系
Java的增强for循环(for-each)本质上是语法糖,编译器会将其转换为迭代器遍历:
// 源代码
for (String s : list) {
System.out.println(s);
}
// 编译后等价于
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
重要提醒:在增强for循环中不能直接修改集合(add/remove),因为迭代器会检测到结构性修改并抛出 ConcurrentModificationException,如果需要遍历时删除,必须显式使用迭代器的 remove() 方法。
常见问题QA
Q1:迭代器模式与for循环,哪个性能更好?
A:对于ArrayList这类随机访问集合,传统for循环(下标索引)略快于迭代器(约5%-10%),但对于LinkedList,迭代器比下标索引快数十倍,因为下标索引每次都要从头遍历。在不确定集合类型时,迭代器是更安全的选择。
Q2:为什么迭代器的remove()方法现在被标记为“废弃倾向”?
A:因为Java 8引入了Collection.removeIf()方法,它更清晰且支持Lambda,remove()方法要求开发者在调用next()之后立即调用remove(),顺序错误会抛异常,容易出错,推荐优先使用 collection.removeIf(predicate)。
Q3:如何实现一个只读的迭代器?
A:在next()方法中返回集合元素的不可变副本(如使用Collections.unmodifiableList),或者在迭代器内部禁用remove()方法,直接抛出UnsupportedOperationException。
Q4:迭代器可以支持倒序遍历吗?
A:可以,只需要在迭代器中维护一个倒序的索引或使用双向链表,并实现hasPrevious()和previous()方法,Java的ListIterator正是这种双向迭代器。
Q5:大数据的迭代器有什么注意事项?
A:迭代器适合“按需加载”场景,但要注意:
- 不要在多线程环境下共享同一个迭代器,除非显式做同步
- 迭代器内部如果持有数据库连接或文件句柄,务必在finally块中关闭
- 可考虑使用回退迭代器(PeekingIterator)来提前查看下一个元素
迭代器模式在JDK源码中的应用
除了常见的集合类,JDK中还有很多迭代器模式的典范:
- java.util.Scanner:通过迭代器的
hasNext()/next()方法来解析输入流 - java.nio.file.DirectoryStream:返回目录下的文件列表,支持迭代器遍历
- java.util.ServiceLoader:加载服务提供者列表,本质也是迭代器模式
- Stream API的内部迭代:Stream的
iterator()方法返回一个Spliterator,它是并行迭代的迭代器
Spliterator 是Java 8引入的增强版迭代器,支持分区遍历和并行处理,是JVM进行流优化的重要基础。
最佳实践与性能优化建议
-
选择适当的迭代器类型:
- 如果只需要顺序遍历,用Iterator
- 如果需要前后遍历或修改元素,用ListIterator
- 如果需要并行处理大数据集,用Spliterator
-
避免在迭代器内部持有过多状态:
迭代器对象可能在多线程环境下被短暂使用,尽量减少内部缓存或预加载
-
使用不可变迭代器确保线程安全:
Collections.unmodifiableCollection(collection).iterator()返回的迭代器不允许修改操作,适合作为API返回值
-
组合迭代器模式与工厂模式:
- 如果你想为同一个集合提供正序、逆序、过滤等多种遍历方式,可以定义一个
IteratorFactory来生成不同的迭代器实例
- 如果你想为同一个集合提供正序、逆序、过滤等多种遍历方式,可以定义一个
-
避免重复创建迭代器对象:
如果集合内容没有变化,但需要多次遍历,可以考虑缓存迭代器(复制一份可重用的)——注意线程安全