1. 迭代器模式:为什么我们需要它?
作为一名有十年经验的Java开发者,我至今还记得第一次面对多种集合类型遍历时的困惑。当时项目中同时使用了数组、ArrayList和自定义链表结构,每次遍历都要写不同的循环代码,不仅重复劳动,还经常因为不熟悉某种集合的内部实现而踩坑。直到学习了迭代器模式,才真正体会到设计模式的精妙之处。
迭代器模式的核心价值在于它提供了一种统一的遍历接口,让我们能够以相同的方式访问不同类型的集合对象。想象一下你去图书馆借书,不管书籍是按照分类摆放、字母顺序排列还是随机堆放,你只需要通过图书检索系统(迭代器)就能找到想要的书籍,而不需要了解书籍实际存放的物理结构。这就是迭代器模式的现实映射。
提示:迭代器模式特别适合以下场景:当你的系统需要处理多种数据结构,但希望对外提供一致的遍历接口时;或者当你希望隐藏集合内部复杂结构,只暴露必要的遍历功能时。
2. 迭代器模式原理深度解析
2.1 模式结构与组件职责
让我们通过一个完整的UML类图来理解迭代器模式的组成:
code复制+-------------------+ +-------------------+
| Aggregate | | Iterator |
+-------------------+ +-------------------+
| +createIterator() |<------>| +first() |
+-------------------+ | +next() |
^ | +isDone() |
| | +currentItem() |
| +-------------------+
| ^
| |
+-------------------+ +-------------------+
| ConcreteAggregate | | ConcreteIterator |
+-------------------+ +-------------------+
| +createIterator() |------->| +first() |
| +getItem() | | +next() |
| +getSize() | | +isDone() |
+-------------------+ | +currentItem() |
+-------------------+
在这个结构中,四个核心组件各司其职:
-
Aggregate(抽象聚合接口):
- 定义创建迭代器对象的接口
- 通常包含一个createIterator()方法
- 可能还包含获取集合大小、获取指定位置元素等方法
-
Iterator(抽象迭代器接口):
- 定义遍历集合的通用操作
- 基本方法包括:first()、next()、isDone()、currentItem()
- 有些实现也会使用hasNext()替代isDone()
-
ConcreteAggregate(具体聚合类):
- 实现Aggregate接口
- 负责创建并返回对应的具体迭代器实例
- 维护集合的实际数据存储
-
ConcreteIterator(具体迭代器类):
- 实现Iterator接口
- 跟踪遍历过程中的当前位置
- 实现对集合元素的实际访问逻辑
2.2 Java集合框架中的迭代器实现
Java集合框架是迭代器模式的经典应用。以ArrayList为例,我们来看它的迭代器实现:
java复制// ArrayList中的迭代器创建
public Iterator<E> iterator() {
return new Itr();
}
// 具体的Itr实现
private class Itr implements Iterator<E> {
int cursor; // 下一个要返回的元素的索引
int lastRet = -1; // 最后一个返回的元素的索引
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
// 其他方法省略...
}
这种实现有几个值得注意的设计点:
- 迭代器作为集合的内部类实现,可以直接访问外部类的私有成员
- 通过modCount机制实现快速失败(fail-fast)的并发修改检测
- 游标设计既简单又高效,适合数组结构的遍历
3. 迭代器模式实战:自定义集合实现
3.1 场景描述与需求分析
假设我们正在开发一个电商系统,需要处理三种商品集合:
- 数组存储的特价商品(性能考虑)
- ArrayList存储的常规商品(灵活性考虑)
- 自定义链表存储的推荐商品(特殊业务需求)
我们需要实现一个统一的商品浏览功能,客户端代码不应该关心商品具体存储在哪种集合中。
3.2 具体实现代码
首先定义商品类:
java复制public class Product {
private String id;
private String name;
private double price;
// 构造方法、getter/setter省略
}
然后定义我们的聚合接口和迭代器接口:
java复制// 商品聚合接口
public interface ProductAggregate {
void add(Product product);
void remove(Product product);
ProductIterator createIterator();
int size();
}
// 商品迭代器接口
public interface ProductIterator {
Product first();
Product next();
boolean isDone();
Product currentItem();
}
实现数组存储的特价商品集合:
java复制public class DiscountProductArray implements ProductAggregate {
private Product[] products;
private int index = 0;
public DiscountProductArray(int size) {
this.products = new Product[size];
}
@Override
public void add(Product product) {
if (index < products.length) {
products[index++] = product;
}
}
// 其他方法实现省略...
@Override
public ProductIterator createIterator() {
return new DiscountProductArrayIterator(this);
}
}
// 对应的迭代器实现
public class DiscountProductArrayIterator implements ProductIterator {
private DiscountProductArray aggregate;
private int currentIndex = 0;
public DiscountProductArrayIterator(DiscountProductArray aggregate) {
this.aggregate = aggregate;
}
@Override
public Product first() {
if (aggregate.size() > 0) {
return aggregate.get(0);
}
return null;
}
// 其他方法实现省略...
}
类似的,我们可以实现ArrayList版本和链表版本的集合。客户端使用时:
java复制public class ProductBrowser {
public void displayProducts(ProductAggregate aggregate) {
ProductIterator iterator = aggregate.createIterator();
System.out.println("商品列表:");
for (Product product = iterator.first();
!iterator.isDone();
product = iterator.next()) {
System.out.printf("%s - %.2f元\n",
product.getName(), product.getPrice());
}
}
}
3.3 实现中的关键考量
-
迭代器生命周期管理:
- 迭代器通常设计为轻量级对象
- 可以考虑使用对象池优化频繁创建的场景
- 注意迭代器与集合的关联关系
-
并发修改处理:
- Java集合的快速失败机制值得借鉴
- 也可以考虑"快照"方式,使迭代器遍历固定版本的数据
-
性能优化:
- 对于数组结构,直接索引访问效率最高
- 对于链表结构,维护当前节点的引用
- 避免在迭代器中执行复杂计算
4. 迭代器模式的高级应用与变体
4.1 内部迭代器 vs 外部迭代器
我们前面讨论的都是外部迭代器(客户端控制迭代过程),其实还有内部迭代器的概念:
java复制// 内部迭代器接口
public interface InternalIterator {
void traverse(Consumer<Product> action);
}
// 使用示例
products.traverse(product -> {
System.out.println(product.getName());
// 其他处理...
});
内部迭代器的特点:
- 集合控制迭代过程
- 客户端提供处理逻辑(通常用回调函数/lambda)
- Java 8的forEach就是内部迭代器的实现
4.2 复合迭代器
当我们需要遍历树形结构或图结构时,可以使用复合迭代器:
java复制public class CompositeIterator implements Iterator<Component> {
private Stack<Iterator<Component>> stack = new Stack<>();
public CompositeIterator(Iterator<Component> iterator) {
stack.push(iterator);
}
@Override
public boolean hasNext() {
if (stack.empty()) {
return false;
}
Iterator<Component> iterator = stack.peek();
if (!iterator.hasNext()) {
stack.pop();
return hasNext();
}
return true;
}
@Override
public Component next() {
Iterator<Component> iterator = stack.peek();
Component component = iterator.next();
if (component instanceof Composite) {
stack.push(component.createIterator());
}
return component;
}
}
这种迭代器使用栈结构实现深度优先遍历,是组合模式与迭代器模式的经典结合。
4.3 过滤迭代器
我们可以创建具有过滤功能的迭代器:
java复制public class FilteringIterator implements Iterator<Product> {
private Iterator<Product> source;
private Predicate<Product> predicate;
private Product nextProduct;
public FilteringIterator(Iterator<Product> source,
Predicate<Product> predicate) {
this.source = source;
this.predicate = predicate;
advance();
}
private void advance() {
nextProduct = null;
while (source.hasNext()) {
Product product = source.next();
if (predicate.test(product)) {
nextProduct = product;
break;
}
}
}
@Override
public boolean hasNext() {
return nextProduct != null;
}
@Override
public Product next() {
if (!hasNext()) throw new NoSuchElementException();
Product result = nextProduct;
advance();
return result;
}
}
使用示例:
java复制// 只遍历价格大于100的商品
Iterator<Product> expensiveProducts =
new FilteringIterator(products.iterator(),
p -> p.getPrice() > 100);
5. 迭代器模式的陷阱与最佳实践
5.1 常见问题与解决方案
问题1:迭代过程中修改集合
这是最常见的错误,解决方案包括:
- 使用快速失败机制(如Java集合)
- 创建集合的副本进行遍历
- 使用并发集合类
问题2:多层嵌套迭代的性能问题
java复制// 低效的嵌套迭代
for (Product p1 : products1) {
for (Product p2 : products2) {
// 比较或处理...
}
}
优化方案:
- 考虑使用索引或哈希加速查找
- 预先处理数据减少嵌套层级
- 使用并行流处理(Java 8+)
问题3:内存泄漏风险
长时间持有迭代器可能导致集合无法被GC回收,特别是在缓存场景中。解决方案:
- 限制迭代器生命周期
- 使用弱引用
- 定期清理过期迭代器
5.2 性能优化技巧
-
针对不同集合类型的优化:
- 数组:直接索引访问
- 链表:维护当前节点引用
- 哈希表:按桶遍历
-
延迟初始化:
java复制public class LazyIterator implements Iterator<Product> { private List<Product> products; private int index = 0; public LazyIterator(Supplier<List<Product>> supplier) { this.products = supplier.get(); // 延迟加载 } // 其他实现... } -
批量处理:
java复制public interface BatchIterator<T> { List<T> nextBatch(int batchSize); boolean hasMore(); }
5.3 设计原则的体现
迭代器模式完美体现了以下几个设计原则:
-
单一职责原则:
- 集合类负责数据存储
- 迭代器类负责遍历逻辑
-
开闭原则:
- 可以添加新的集合类型而不影响现有代码
- 可以添加新的迭代方式而不修改集合类
-
迪米特法则:
- 客户端只需要知道迭代器接口
- 不需要了解集合内部结构
6. 现代编程语言中的迭代器模式演进
6.1 Java 8 Stream API
Java 8引入的Stream API实际上是迭代器模式的升级版:
java复制products.stream()
.filter(p -> p.getPrice() > 100)
.sorted(comparing(Product::getName))
.map(Product::getName)
.forEach(System.out::println);
特点:
- 链式调用
- 惰性求值
- 并行处理支持
6.2 C#的yield return
C#通过yield关键字简化迭代器实现:
csharp复制public IEnumerable<Product> GetExpensiveProducts() {
foreach (var product in products) {
if (product.Price > 100) {
yield return product; // 状态自动保存
}
}
}
6.3 JavaScript的迭代器协议
ES6引入的迭代器协议:
javascript复制class ProductCollection {
constructor() {
this.products = [];
}
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
return index < this.products.length ?
{ value: this.products[index++], done: false } :
{ done: true };
}
};
}
}
6.4 函数式编程中的迭代器
在函数式语言中,迭代器通常与高阶函数结合:
scala复制val expensiveProducts = products.filter(_.price > 100)
expensiveProducts.foreach(println)
特点:
- 不可变集合
- 纯函数操作
- 组合式数据处理
7. 实际项目中的经验分享
在我参与的一个大型电商平台项目中,我们遇到了商品搜索结果的多样化展示需求。系统需要支持:
- 基本列表视图
- 网格视图
- 瀑布流视图
- 地图标记视图
通过迭代器模式的灵活应用,我们实现了:
java复制public interface SearchResult {
Iterator<Product> getSimpleIterator();
Iterator<RichProduct> getRichIterator();
Iterator<LocationAwareProduct> getMapIterator();
// 其他专用迭代器...
}
关键收获:
- 视图与数据分离:每种视图只需要关注自己需要的迭代器接口
- 性能优化:为不同视图提供专门优化的迭代器实现
- 可扩展性:新增视图类型不需要修改核心搜索逻辑
另一个案例是在处理大数据量分页时,我们实现了基于游标的迭代器:
java复制public class PagingIterator implements Iterator<List<Product>> {
private ProductDAO dao;
private int pageSize;
private String cursor;
private List<Product> currentBatch;
@Override
public boolean hasNext() {
if (currentBatch == null || currentBatch.size() < pageSize) {
return false;
}
return dao.hasMoreProducts(cursor);
}
@Override
public List<Product> next() {
currentBatch = dao.fetchNextPage(cursor, pageSize);
cursor = currentBatch.get(currentBatch.size()-1).getId();
return currentBatch;
}
}
这种实现:
- 避免了一次性加载所有数据
- 支持断点续传
- 减少内存占用
8. 测试迭代器实现的要点
编写高质量的迭代器测试用例需要考虑以下方面:
-
基本功能测试:
java复制@Test public void testIterationOrder() { List<String> expected = Arrays.asList("A", "B", "C"); List<String> actual = new ArrayList<>(); Iterator<String> it = collection.iterator(); while (it.hasNext()) { actual.add(it.next()); } assertEquals(expected, actual); } -
边界条件测试:
java复制@Test public void testEmptyCollection() { Iterator<String> it = emptyCollection.iterator(); assertFalse(it.hasNext()); assertThrows(NoSuchElementException.class, () -> it.next()); } -
并发修改测试:
java复制@Test public void testConcurrentModification() { Iterator<String> it = collection.iterator(); collection.add("D"); // 修改集合 assertThrows(ConcurrentModificationException.class, () -> it.next()); } -
性能测试:
java复制@Test public void testIterationPerformance() { long start = System.nanoTime(); for (Product p : largeCollection) { // 模拟处理 } long duration = System.nanoTime() - start; assertTrue(duration < TimeUnit.MILLISECONDS.toNanos(100)); } -
资源清理测试:
java复制@Test public void testIteratorResourceCleanup() { try (CloseableIterator<Product> it = dbCollection.iterator()) { // 使用迭代器 } // 自动关闭资源 verify(dbConnection).close(); }
9. 迭代器模式与其他模式的关系
9.1 与组合模式结合
如前所述,复合迭代器可以遍历树形结构,这是两种模式的经典组合:
java复制public class MenuItem implements MenuComponent {
// 叶子节点实现...
}
public class Menu implements MenuComponent {
private List<MenuComponent> children = new ArrayList<>();
public Iterator<MenuComponent> iterator() {
return new CompositeIterator(children.iterator());
}
// 其他实现...
}
9.2 与工厂方法模式结合
集合的iterator()方法通常使用工厂方法模式:
java复制public abstract class AbstractCollection {
public abstract Iterator<E> iterator(); // 工厂方法
// 其他实现...
}
9.3 与访问者模式对比
两种模式都用于遍历对象结构,但侧重点不同:
| 特性 | 迭代器模式 | 访问者模式 |
|---|---|---|
| 主要目的 | 遍历集合元素 | 对元素执行操作 |
| 控制权 | 客户端控制迭代 | 访问者控制遍历 |
| 元素类型 | 通常处理同类型元素 | 可以处理不同类型元素 |
| 扩展性 | 易于添加新集合类型 | 易于添加新操作 |
| 适用场景 | 需要多种遍历方式 | 需要对元素执行多种操作 |
9.4 与策略模式结合
可以为不同的遍历算法创建不同的迭代器策略:
java复制public interface TraversalStrategy {
Iterator<Node> createIterator(Node root);
}
public class BreadthFirstStrategy implements TraversalStrategy {
// 广度优先实现...
}
public class DepthFirstStrategy implements TraversalStrategy {
// 深度优先实现...
}
10. 从迭代器模式看软件设计哲学
迭代器模式的成功揭示了几个重要的软件设计理念:
-
关注点分离:
- 数据存储与数据遍历分离
- 使每个类保持单一职责
-
抽象的力量:
- 通过Iterator接口抽象遍历操作
- 客户端只依赖抽象,不依赖具体实现
-
封装的艺术:
- 隐藏集合内部结构
- 暴露最小必要接口
-
迭代的普遍性:
- 迭代是软件中最常见的操作之一
- 好的迭代抽象能显著提升代码质量
-
语言设计的启示:
- 现代语言都内置了迭代支持
- 如Java的for-each、C#的foreach、Python的iterable
在我多年的开发经验中,迭代器模式教会我最重要的不是模式本身,而是这种"分离变化方向"的设计思想。当你发现某个类经常因为不同原因而修改时,就是考虑拆分职责的时候了。