迭代器模式:统一遍历接口
迭代器模式,说白了就是给集合类提供一个统一的遍历方式。你想想看,ArrayList用数组存数据,LinkedList用链表存数据,HashMap用哈希表存数据——如果每种集合都要自己写遍历逻辑,那代码得多乱?
我个人习惯把迭代器理解成「遥控器」。集合是电视机,迭代器就是遥控器上的上下键。你不需要知道电视机内部怎么工作的,按上就上一个台,按下就下一个台。嗯,就是这么回事。
为什么需要迭代器模式?
我在项目中遇到过这样一个场景:系统里同时用了ArrayList和HashSet来存储用户权限数据。业务逻辑里需要遍历这些权限做校验。如果没有统一接口,就得写两套遍历代码——一套用for循环+get(i),一套用for-each。后来加了个TreeSet,又得改一遍。
迭代器模式解决的核心问题就三个:
- 统一遍历接口:不管底层是数组、链表还是哈希表,遍历方式都一样
- 解耦集合与遍历:集合类只负责存数据,遍历逻辑交给迭代器
- 支持多种遍历方式:可以正序遍历、倒序遍历、过滤遍历,而不修改集合本身
核心思想:将遍历行为从集合类中抽离出来,封装成独立的迭代器对象。
模式结构
迭代器模式的结构其实很简单,就四个角色:
| 角色 | 职责 | 类比 |
|---|---|---|
| Iterator(抽象迭代器) | 定义遍历接口,如hasNext()、next() | 遥控器的按键定义 |
| ConcreteIterator(具体迭代器) | 实现具体的遍历逻辑 | 某个品牌遥控器的内部电路 |
| Aggregate(抽象集合) | 定义创建迭代器的方法 | 电视机接口 |
| ConcreteAggregate(具体集合) | 实现集合存储,并返回对应的迭代器 | 具体品牌的电视机 |
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的时候,可以想想背后那个默默工作的迭代器对象。