文章目录
引言
在日常开发中,我们经常需要遍历集合中的元素。无论是简单的数组、List还是复杂的树形结构,遍历操作都是不可或缺的。然而,你是否思考过:为什么Java集合框架能够让我们用统一的方式遍历不同类型的集合?这背后的设计思想就是今天我们要深入探讨的迭代器模式。
迭代器模式是一种行为型设计模式,它将遍历集合的职责从集合对象中分离出来,为我们提供了一种统一、安全且高效的遍历方式。本文将带你从原理到实践,全面掌握迭代器模式的核心思想与实现技巧。
什么是迭代器模式?
迭代器模式(Iterator Pattern),又称为游标(Cursor)模式,其核心定义是:
迭代器提供一种对容器对象中的各个元素进行访问的方法,而又不需要暴露该对象的内部细节。
简单来说,迭代器模式就是为集合对象提供一个"导游",让这个"导游"负责带领我们参观集合中的每个元素,而不需要我们了解这个"景点"(集合)内部是如何组织的。
为什么需要迭代器模式?
在没有迭代器模式的情况下,我们通常会这样遍历集合:
// 遍历数组
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
// 遍历链表
for (Node node = head; node != null; node = node.next) {
System.out.println(node.value);
}
这种方式存在几个问题:
- 暴露了集合的内部结构
- 每种集合都需要不同的遍历方式
- 重复代码多,难以复用
- 集合结构变化时,遍历代码需要大量修改
而迭代器模式通过封装遍历逻辑,完美解决了这些问题。
迭代器模式的UML结构
迭代器模式包含四个关键角色:
- 抽象集合类(Aggregate):定义创建迭代器的方法
- 具体集合类(ConcreteAggregate):实现抽象集合类,创建具体的迭代器
- 抽象迭代器类(Iterator):定义统一的迭代器接口
- 具体迭代器类(ConcreteIterator):实现具体的遍历逻辑
Code:从零构建迭代器
让我们通过一个实际案例,从零开始实现迭代器模式。假设我们需要遍历一个主题(Topic)列表。
1. 定义主题类
首先,创建一个简单的Topic类:
public class Topic {
private String name;
public Topic(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2. 定义抽象迭代器接口
创建迭代器的核心接口,定义基本的遍历方法:
public interface IteratorIterator<E> {
void reset(); // 重置为第一个元素
E next(); // 获取下一个元素
E currentItem(); // 检索当前元素
boolean hasNext(); // 判断是否还有下一个元素
}
3. 定义抽象集合接口
public interface ListList<E> {
IteratorIterator<E> iterator(); // 创建迭代器
}
4. 实现具体迭代器
为Topic列表实现具体的迭代器:
public class TopicIterator implements IteratorIterator<Topic> {
private Topic[] topics;
private int position;
public TopicIterator(Topic[] topics) {
this.topics = topics;
this.position = 0;
}
@Override
public void reset() {
position = 0;
}
@Override
public Topic next() {
return topics[position++];
}
@Override
public Topic currentItem() {
return topics[position];
}
@Override
public boolean hasNext() {
return position < topics.length;
}
}
5. 实现具体集合类
public class TopicList implements ListList<Topic> {
private Topic[] topics;
public TopicList(Topic[] topics) {
this.topics = topics;
}
@Override
public IteratorIterator<Topic> iterator() {
return new TopicIterator(topics);
}
}
6. 客户端使用示例
public class IteratorDemo {
public static void main(String[] args) {
// 创建主题数组
Topic[] topics = new Topic[5];
topics[0] = new Topic("Java基础");
topics[1] = new Topic("设计模式");
topics[2] = new Topic("Spring框架");
topics[3] = new Topic("微服务架构");
topics[4] = new Topic("分布式系统");
// 创建集合对象
ListList<Topic> topicList = new TopicList(topics);
// 获取迭代器
IteratorIterator<Topic> iterator = topicList.iterator();
// 遍历集合
System.out.println("使用迭代器遍历主题列表:");
while (iterator.hasNext()) {
Topic currentTopic = iterator.next();
System.out.println("- " + currentTopic.getName());
}
}
}
输出结果:
使用迭代器遍历主题列表:
- Java基础
- 设计模式
- Spring框架
- 微服务架构
- 分布式系统
迭代器模式的核心价值
1. 职责分离:关注点分离原则的完美体现
迭代器模式最核心的价值在于职责分离:
- 集合类只关注数据的存储和管理
- 迭代器只关注数据的遍历
- 客户端只关注业务逻辑
这种分离使得系统各部分的职责更加清晰,降低了代码的耦合度。
2. 遍历算法与集合实现的解耦
通过迭代器,我们可以使用相同的代码遍历不同内部结构的集合:
// 遍历数组实现的集合
ListList<Topic> arrayBasedList = new TopicList(topics);
IteratorIterator<Topic> arrayIterator = arrayBasedList.iterator();
// 遍历链表实现的集合(假设存在)
ListList<Topic> linkedList = new TopicLinkedList(topics);
IteratorIterator<Topic> linkedIterator = linkedList.iterator();
// 使用完全相同的代码遍历
while (arrayIterator.hasNext()) {
System.out.println(arrayIterator.next().getName());
}
while (linkedIterator.hasNext()) {
System.out.println(linkedIterator.next().getName());
}
3. 隐藏集合内部结构
迭代器模式使集合的内部结构对客户端透明。客户端不需要知道集合是数组、链表还是树形结构,只需通过统一的迭代器接口进行遍历。
Java集合框架中的迭代器实现
实际上,Java标准库已经为我们提供了完善的迭代器实现。我们日常使用的Iterator
和Iterable
接口就是迭代器模式的典型应用:
import java.util.*;
public class JavaIteratorExample {
public static void main(String[] args) {
// 创建ArrayList
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("Go");
// 使用迭代器遍历
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String language = iterator.next();
System.out.println(language);
}
// 使用增强for循环(底层也是迭代器)
for (String language : list) {
System.out.println(language);
}
}
}
在Java中,Iterable
接口定义了iterator()
方法,而Iterator
接口定义了next()
和hasNext()
方法,这与我们之前自定义的实现非常相似。
迭代器模式的高级应用
1. 安全迭代器与快速失败机制
在多线程环境下,迭代过程中集合被修改会导致问题。Java集合框架提供了两种解决方案:
- 安全迭代器:创建迭代器时复制整个集合(如
CopyOnWriteArrayList
) - 快速失败机制:检测到并发修改时立即抛出
ConcurrentModificationException
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
Iterator<String> iterator = list.iterator();
list.add("C"); // 修改集合
// 下次调用next()会抛出ConcurrentModificationException
try {
iterator.next();
} catch (ConcurrentModificationException e) {
System.out.println("检测到并发修改!");
}
2. 双向迭代器
标准迭代器只能向前遍历,但有时我们需要双向遍历。Java提供了ListIterator
接口:
List<String> list = Arrays.asList("Java", "Python", "Go");
ListIterator<String> listIterator = list.listIterator();
// 向前遍历
while (listIterator.hasNext()) {
System.out.println("向前: " + listIterator.next());
}
// 向后遍历
while (listIterator.hasPrevious()) {
System.out.println("向后: " + listIterator.previous());
}
3. 自定义过滤迭代器
我们可以创建一个装饰器模式的迭代器,实现特定条件的过滤:
public class FilterIterator<T> implements Iterator<T> {
private Iterator<T> iterator;
private Predicate<T> predicate;
private T nextElement;
private boolean hasNext;
public FilterIterator(Iterator<T> iterator, Predicate<T> predicate) {
this.iterator = iterator;
this.predicate = predicate;
advance();
}
private void advance() {
hasNext = false;
while (iterator.hasNext()) {
T element = iterator.next();
if (predicate.test(element)) {
nextElement = element;
hasNext = true;
return;
}
}
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public T next() {
if (!hasNext) {
throw new NoSuchElementException();
}
T result = nextElement;
advance();
return result;
}
}
// 使用示例
List<String> languages = Arrays.asList("Java", "JavaScript", "Python", "Go");
Iterator<String> filteredIterator = new FilterIterator<>(
languages.iterator(),
s -> s.startsWith("J")
);
while (filteredIterator.hasNext()) {
System.out.println(filteredIterator.next()); // 输出: Java, JavaScript
}
为什么在Java中边遍历边删除会出错?
这是许多Java开发者都遇到过的问题:在使用普通for循环或迭代器遍历时,直接删除集合元素会导致ConcurrentModificationException
。
问题原因
Java集合框架的大多数实现(如ArrayList、HashMap)都采用了"快速失败"机制。当创建迭代器时,会记录集合的修改次数(modCount)。在迭代过程中,如果检测到集合被直接修改(通过集合自身的remove/add方法而非迭代器的remove方法),就会抛出异常。
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 错误方式:边遍历边删除
for (String item : list) {
if ("B".equals(item)) {
list.remove(item); // 会抛出ConcurrentModificationException
}
}
正确解决方案
- 使用迭代器的remove()方法
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("B".equals(item)) {
iterator.remove(); // 安全删除
}
}
- 使用removeIf()方法(Java 8+)
list.removeIf(item -> "B".equals(item));
- 使用临时集合
List<String> toRemove = new ArrayList<>();
for (String item : list) {
if ("B".equals(item)) {
toRemove.add(item);
}
}
list.removeAll(toRemove);
- 使用CopyOnWriteArrayList(仅适用于读多写少的场景)
List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("A", "B", "C"));
for (String item : list) {
if ("B".equals(item)) {
list.remove(item); // 安全,因为CopyOnWriteArrayList会创建新副本
}
}
迭代器模式的优势与劣势
优势
- 单一职责原则:将遍历逻辑从集合类中分离,使每个类的职责更加清晰
- 开闭原则:新增集合类型时,只需添加新的迭代器实现,无需修改现有代码
- 避免重复代码:统一了不同集合的遍历方式,减少了重复代码
- 隐藏内部结构:客户端无需了解集合的内部实现细节
- 支持多种遍历方式:可以为同一集合提供多种迭代器实现(如正向、反向、过滤等)
劣势
- 增加类的数量:每个集合类型都需要对应的迭代器类
- 可能增加系统复杂度:对于简单场景,迭代器模式可能显得过于复杂
- 性能考虑:某些迭代器实现可能会带来额外的性能开销
实际应用场景
- 集合框架:Java的Collections Framework是迭代器模式的典型应用
- 文件系统遍历:遍历目录结构时使用迭代器隐藏文件系统的复杂性
- 数据库结果集:JDBC的ResultSet接口本质上是一个迭代器
- GUI组件:遍历树形结构的UI组件(如JTree)
- 流式处理:Java 8的Stream API底层使用了迭代器模式
最佳实践建议
- 优先使用标准库:Java集合框架已经提供了完善的迭代器实现,除非有特殊需求,否则不要重复造轮子
- 考虑并发安全:在多线程环境下,注意选择合适的迭代器实现
- 合理选择迭代方式:根据场景选择普通迭代器、列表迭代器或流式API
- 避免在迭代过程中修改集合:除非使用迭代器提供的remove方法
- 理解底层实现:了解不同集合类迭代器的性能特性,如ArrayList的随机访问效率高于LinkedList
总结
迭代器模式是一种简单但极其重要的设计模式,它通过将遍历职责从集合中分离出来,实现了关注点分离,使代码更加清晰、灵活和可维护。虽然在日常开发中我们更多是直接使用Java集合框架提供的迭代器,但理解其背后的设计思想对于编写高质量代码至关重要。
掌握迭代器模式不仅能帮助我们更好地使用Java集合框架,还能启发我们在设计复杂系统时如何实现职责分离,使系统各部分更加独立、可测试和可扩展。
关键要点回顾:
- 迭代器模式的核心是将遍历逻辑与集合实现分离
- Java的Iterator和Iterable接口是迭代器模式的标准实现
- 边遍历边删除时应使用迭代器的remove()方法或Java 8的removeIf()
- 迭代器模式符合单一职责原则和开闭原则
- 在实际开发中优先使用标准库提供的迭代器实现