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

迭代器模式,说白了就是给集合类提供一个统一的遍历方式。你想想看,ArrayList用数组存数据,LinkedList用链表存数据,HashMap用哈希表存数据——如果每种集合都要自己写遍历逻辑,那代码得多乱?

我个人习惯把迭代器理解成「遥控器」。集合是电视机,迭代器就是遥控器上的上下键。你不需要知道电视机内部怎么工作的,按上就上一个台,按下就下一个台。嗯,就是这么回事。

为什么需要迭代器模式?

我在项目中遇到过这样一个场景:系统里同时用了ArrayList和HashSet来存储用户权限数据。业务逻辑里需要遍历这些权限做校验。如果没有统一接口,就得写两套遍历代码——一套用for循环+get(i),一套用for-each。后来加了个TreeSet,又得改一遍。

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

  • 统一遍历接口:不管底层是数组、链表还是哈希表,遍历方式都一样
  • 解耦集合与遍历:集合类只负责存数据,遍历逻辑交给迭代器
  • 支持多种遍历方式:可以正序遍历、倒序遍历、过滤遍历,而不修改集合本身

核心思想:将遍历行为从集合类中抽离出来,封装成独立的迭代器对象。

模式结构

迭代器模式的结构其实很简单,就四个角色:

角色 职责 类比
Iterator(抽象迭代器) 定义遍历接口,如hasNext()、next() 遥控器的按键定义
ConcreteIterator(具体迭代器) 实现具体的遍历逻辑 某个品牌遥控器的内部电路
Aggregate(抽象集合) 定义创建迭代器的方法 电视机接口
ConcreteAggregate(具体集合) 实现集合存储,并返回对应的迭代器 具体品牌的电视机
迭代器模式结构图 «接口» Iterator + hasNext(): boolean + next(): Object «接口» Aggregate + createIterator(): Iterator ConcreteIterator - cursor: int + hasNext(): boolean ConcreteAggregate - data: Object[] + createIterator(): Iterator 创建 客户端使用

Java实现:手写ArrayList迭代器

Java的java.util.Iterator接口大家都很熟悉了。但你知道它底层是怎么实现的吗?我带你手写一个简易版。

// 抽象迭代器接口
public interface MyIterator<T> {
    boolean hasNext();
    T next();
}

// 抽象集合接口
public interface MyList<T> {
    MyIterator<T> iterator();
    void add(T item);
    int size();
}

// 具体集合:简易ArrayList
public class MyArrayList<T> implements MyList<T> {
    private Object[] data;
    private int size;
    private static final int DEFAULT_CAPACITY = 10;
    
    public MyArrayList() {
        data = new Object[DEFAULT_CAPACITY];
        size = 0;
    }
    
    @Override
    public void add(T item) {
        if (size == data.length) {
            // 扩容
            Object[] newData = new Object[data.length * 2];
            System.arraycopy(data, 0, newData, 0, data.length);
            data = newData;
        }
        data[size++] = item;
    }
    
    @Override
    public int size() {
        return size;
    }
    
    @Override
    public MyIterator<T> iterator() {
        return new ArrayListIterator();
    }
    
    // 具体迭代器 —— 内部类
    private class ArrayListIterator implements MyIterator<T> {
        private int cursor = 0;
        
        @Override
        public boolean hasNext() {
            return cursor < size;
        }
        
        @Override
        @SuppressWarnings("unchecked")
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException("没有更多元素了");
            }
            return (T) data[cursor++];
        }
    }
}

// 客户端使用
public class Client {
    public static void main(String[] args) {
        MyList<String> list = new MyArrayList<>();
        list.add("Java");
        list.add("设计模式");
        list.add("迭代器");
        
        MyIterator<String> it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

个人经验:内部类实现迭代器有个好处——可以直接访问外部类的私有成员。但要注意,如果外部类修改了集合内容,迭代器可能会失效。Java的ArrayList迭代器有fail-fast机制,就是通过modCount来检测并发修改。

C++ STL迭代器:更底层的设计

C++的STL迭代器跟Java的迭代器思路不太一样。Java的迭代器是对象,C++的迭代器更像是指针的泛化。说白了,C++迭代器就是让你能用指针的语法(*it、it++、it != end)来遍历任何容器。

#include <iostream>
#include <vector>
#include <list>
#include <algorithm>

int main() {
    // vector迭代器 —— 随机访问迭代器
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::cout << "Vector遍历: ";
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    
    // list迭代器 —— 双向迭代器
    std::list<std::string> lst = {"C++", "STL", "Iterator"};
    std::cout << "List遍历: ";
    for (auto it = lst.begin(); it != lst.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    
    // 使用STL算法 + 迭代器
    std::vector<int> numbers = {5, 3, 8, 1, 9};
    std::sort(numbers.begin(), numbers.end());
    std::cout << "排序后: ";
    for (int n : numbers) {  // 范围for循环底层也是迭代器
        std::cout << n << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

C++迭代器分为五种类型,每种支持的操作不同:

迭代器类型 支持操作 典型容器
输入迭代器 ++it, *it, it != end istream_iterator
输出迭代器 ++it, *it = value ostream_iterator
前向迭代器 输入+输出,可多次遍历 forward_list
双向迭代器 前向 + --it list, set, map
随机访问迭代器 双向 + it+n, it-n, it[n] vector, deque, array

关键区别:Java迭代器是对象,有状态(cursor指针);C++迭代器是轻量级值类型,通常就是指针的封装,可以拷贝、赋值。

避坑指南

我曾经在一个项目中踩过迭代器的坑,分享几个经验:

  • 遍历时不要修改集合结构:Java的ArrayList在for-each循环里调用remove()会抛ConcurrentModificationException。正确做法是用迭代器的remove()方法,或者用CopyOnWriteArrayList。
  • C++迭代器失效问题:vector在插入或删除元素后,之前的迭代器可能全部失效。我见过有人保存了vector的迭代器,然后在后面插入元素后继续用,结果程序崩溃。
  • 不要返回内部迭代器的引用:如果迭代器是内部类对象,返回它的引用可能导致外部类被意外持有,造成内存泄漏。

特别注意:Java的Iterator.remove()方法只能在调用next()之后调用一次。连续调用两次remove()会抛IllegalStateException。这是很多新手容易犯的错。

迭代器模式的实际应用

除了集合遍历,迭代器模式在很多地方都有应用:

  • 数据库游标:ResultSet的next()方法本质上就是迭代器模式
  • 文件读取:BufferedReader的readLine()配合while循环,也是迭代器思想
  • 树形结构遍历:组合模式+迭代器模式,可以实现树的深度优先或广度优先遍历
  • 分页查询:每次取一页数据,用hasNext判断是否还有下一页

我个人觉得,迭代器模式是设计模式里最「润物细无声」的一个。你每天都在用,但可能没意识到它是个模式。Java的for-each循环、C++的范围for循环,底层都是迭代器。你想想看,如果没有迭代器,我们写代码得有多痛苦?

好了,迭代器模式就聊到这里。记住一句话:迭代器让遍历变得统一,让集合变得简单。下次你写for-each的时候,可以想想背后那个默默工作的迭代器对象。


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