迭代器模式:统一集合遍历接口

迭代器模式,说白了就是给集合类提供一个统一的遍历方式。你想想看,Java 里那么多集合——ArrayList、LinkedList、HashSet,它们的底层结构完全不同。如果没有迭代器,你得为每种集合写一套遍历代码,那得多痛苦?

我个人习惯把迭代器模式理解成「遍历的标准化接口」。它让你不用关心集合内部怎么存数据,只要调用 next() 和 hasNext() 就能挨个访问元素。嗯,这里有个关键点——它不暴露集合的内部表示。你拿到的只是一个迭代器,根本不知道底层是数组还是链表。

为什么需要迭代器模式?

我在项目中遇到过这样一个场景:系统里同时用了 ArrayList 和 TreeSet,业务逻辑需要遍历这两种集合做数据合并。如果没有迭代器,我得写两套遍历逻辑——ArrayList 用 for 循环下标,TreeSet 用增强 for 或者迭代器。代码里到处都是 if-else 判断集合类型,维护起来简直噩梦。

迭代器模式解决了三个核心问题:

  • 统一遍历接口:不管什么集合,遍历方式都一样
  • 隐藏内部结构:调用方不需要知道集合底层是数组、链表还是树
  • 支持多种遍历方式:同一个集合可以有不同的迭代器实现

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

Java 中的 Iterator 接口

Java 标准库早就内置了迭代器模式。你看 java.util.Iterator 接口,就三个核心方法:

public interface Iterator<E> {
    boolean hasNext();  // 是否还有下一个元素
    E next();           // 返回下一个元素
    default void remove() {  // 移除当前元素(可选操作)
        throw new UnsupportedOperationException();
    }
}

所有集合类都实现了 Iterable 接口,返回自己的迭代器。这就是为什么你能用增强 for 循环遍历任何集合——底层其实就是调用了 iterator() 方法。

我记得有一次排查性能问题,发现同事在遍历 ArrayList 时用了迭代器,但 ArrayList 的随机访问用 for 循环更快。迭代器模式虽然统一了接口,但不同集合的性能特性还是要注意的。

自定义双向迭代器

Java 标准迭代器只支持正序遍历。但实际业务中,我们经常需要倒序遍历。比如展示操作日志,最新的日志在最前面。这时候如果只能正序遍历,你得先全部加载到内存再反转,效率很低。

下面我实现一个支持正序和倒序遍历的列表,核心是提供两个迭代器:

import java.util.Iterator;
import java.util.NoSuchElementException;

public class BidirectionalList<T> implements Iterable<T> {
    private Object[] elements;
    private int size;
    
    public BidirectionalList(int capacity) {
        elements = new Object[capacity];
        size = 0;
    }
    
    public void add(T element) {
        if (size >= elements.length) {
            throw new ArrayIndexOutOfBoundsException("列表已满");
        }
        elements[size++] = element;
    }
    
    // 正序迭代器
    @Override
    public Iterator<T> iterator() {
        return new ForwardIterator();
    }
    
    // 倒序迭代器
    public Iterator<T> reverseIterator() {
        return new ReverseIterator();
    }
    
    // 正序迭代器实现
    private class ForwardIterator implements Iterator<T> {
        private int currentIndex = 0;
        
        @Override
        public boolean hasNext() {
            return currentIndex < size;
        }
        
        @Override
        @SuppressWarnings("unchecked")
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return (T) elements[currentIndex++];
        }
    }
    
    // 倒序迭代器实现
    private class ReverseIterator implements Iterator<T> {
        private int currentIndex = size - 1;
        
        @Override
        public boolean hasNext() {
            return currentIndex >= 0;
        }
        
        @Override
        @SuppressWarnings("unchecked")
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return (T) elements[currentIndex--];
        }
    }
}

使用起来非常直观:

BidirectionalList<String> list = new BidirectionalList<>(5);
list.add("A");
list.add("B");
list.add("C");

// 正序遍历
for (String s : list) {
    System.out.print(s + " ");  // 输出: A B C
}

// 倒序遍历
Iterator<String> reverseIt = list.reverseIterator();
while (reverseIt.hasNext()) {
    System.out.print(reverseIt.next() + " ");  // 输出: C B A
}

设计要点:迭代器内部持有集合的引用,但只通过接口方法访问数据。这样即使集合内部结构变化,迭代器代码也不需要改。

迭代器模式的核心结构

下面这张图展示了迭代器模式的核心角色和它们之间的关系:

迭代器模式结构图 Aggregate(聚合接口) + createIterator(): Iterator ConcreteAggregate + createIterator(): Iterator Iterator(迭代器接口) + hasNext(): boolean + next(): Object ConcreteIterator + hasNext(): boolean + next(): Object 创建 创建 Client(客户端) 使用 使用

这张图里四个角色:

  • Aggregate:聚合接口,定义创建迭代器的方法
  • ConcreteAggregate:具体聚合类,比如 ArrayList、LinkedList
  • Iterator:迭代器接口,定义 hasNext() 和 next()
  • ConcreteIterator:具体迭代器,实现遍历逻辑

实际业务中的迭代器应用

迭代器模式在真实项目中无处不在。除了 Java 集合框架,我还在这些场景中用过:

场景 说明
数据库游标 ResultSet 本质上就是一个迭代器,逐行读取查询结果
文件行读取 BufferedReader 的 readLine() 配合循环,就是迭代器模式
树形结构遍历 组织架构树、菜单树,可以用迭代器实现深度优先或广度优先遍历
分页查询 每次取一页数据,用迭代器封装分页逻辑,调用方无感知

避坑指南:我曾经在遍历集合时直接调用集合的 remove() 方法,结果抛出了 ConcurrentModificationException。迭代器遍历期间,不要通过集合自身修改结构。正确的做法是用迭代器的 remove() 方法,或者用 CopyOnWriteArrayList 这类线程安全集合。

迭代器模式的优缺点

优点很明显:

  • 遍历与集合解耦:遍历逻辑封装在迭代器里,集合只负责存储
  • 支持多种遍历:同一个集合可以有不同的迭代器,比如正序、倒序、过滤
  • 简化客户端代码:调用方不需要知道集合内部结构

缺点也得提一下:

  • 增加类数量:每个集合类都需要对应的迭代器实现
  • 遍历过程中修改集合有风险:需要额外处理并发修改问题

我的建议:如果你在设计一个需要被频繁遍历的数据结构,优先考虑实现 Iterable 接口。这样你的类就能直接用在增强 for 循环里,也兼容 Java 的 Stream API。说白了,这是让你的类「更 Java」的做法。

迭代器模式看起来简单,但它是很多复杂设计的基础。比如组合模式里遍历树形结构,就用到了迭代器。再比如 Java 8 的 Stream API,底层也是基于迭代器实现的惰性求值。理解了这个模式,你再看很多框架源码会轻松很多。


公众号:蓝海资料掘金营,微信 deep3321