【作者简介】 "琢磨先生"--资深系统架构师、985高校计算机硕士,长期从事大中型软件开发和技术研究,每天分享Java硬核知识和主流工程技术,欢迎点赞收藏!
一、迭代器模式的核心思想
在软件开发中,集合数据的遍历是一个非常常见的需求。无论是数组、链表、树结构还是更复杂的集合类,都需要提供一种方式来访问其中的元素。如果让集合类自身实现遍历逻辑,会导致集合类的职责过重,并且不同集合类的遍历方式差异会导致客户端代码复杂化。迭代器模式(Iterator Pattern)正是为了解决这一问题而诞生的,它将集合对象的遍历操作从集合类中分离出来,封装成一个独立的迭代器对象,使得客户端无需关心集合的内部结构,只需通过统一的接口来访问集合元素。
迭代器模式的核心思想是 "一个职责分离原则的应用",它遵循单一职责原则,将集合对象的存储和遍历操作分开。这样做有几个显著的优点:首先,客户端代码与具体集合类解耦,客户端只需要面对迭代器接口,而无需了解集合的具体实现;其次,不同的集合类可以提供不同的迭代器实现,从而支持多种遍历方式;最后,迭代器模式还支持在不暴露集合内部表示的情况下访问集合元素,提高了数据的封装性。
从设计模式的分类来看,迭代器模式属于行为型模式,它关注对象之间的交互行为,通过定义迭代器接口来规范遍历操作。在 Java 语言中,迭代器模式已经被广泛应用在集合框架中,例如我们常用的Iterator
接口和Iterable
接口就是迭代器模式的典型实现。掌握迭代器模式不仅有助于我们更好地理解 Java 集合框架的工作原理,还能在自定义数据结构时提供优雅的遍历解决方案。
二、迭代器模式的核心角色

(一)抽象迭代器(Iterator)
定义遍历集合元素的接口,通常包含以下几个核心方法:
hasNext()
:判断集合中是否还有下一个元素next()
:返回集合中的下一个元素remove()
(可选):删除迭代器当前指向的元素
在 Java 中,java.util.Iterator
接口就是抽象迭代器的典型实现,所有具体迭代器都需要实现这个接口。这个接口定义了遍历集合的基本操作,客户端代码通过这些方法来访问集合元素,而无需关心具体集合的类型。
(二)具体迭代器(Concrete Iterator)
实现抽象迭代器接口,具体负责对特定集合的遍历。具体迭代器需要维护当前遍历的位置,通常会持有一个对具体集合对象的引用。例如,ArrayList
的迭代器实现会维护一个索引变量,记录当前遍历到的位置,通过索引来访问集合中的元素。
具体迭代器在实现时,需要根据具体集合的结构来实现遍历逻辑。对于链表结构的集合,迭代器可能需要维护一个指向当前节点的指针;对于树结构的集合,迭代器可能需要实现深度优先或广度优先的遍历逻辑。无论集合的内部结构如何复杂,具体迭代器都将遍历逻辑封装起来,对外提供统一的访问接口。
(三)抽象聚合(Aggregate)
定义创建迭代器对象的接口,通常包含一个iterator()
方法,用于返回一个能够遍历当前聚合对象的迭代器。在 Java 中,java.lang.Iterable
接口就是抽象聚合的代表,任何实现了Iterable
接口的类都可以使用foreach
循环进行遍历。
Iterable
接口只有一个抽象方法iterator()
,它的作用是为聚合对象创建一个迭代器。通过将聚合对象定义为Iterable
,客户端代码可以统一地对不同类型的集合进行遍历,而无需关心具体的集合类型。
(四)具体聚合(Concrete Aggregate)
实现抽象聚合接口,返回具体迭代器的实例。具体聚合类通常是实际的集合类,例如ArrayList
、LinkedList
、HashSet
等。这些类实现了Iterable
接口的iterator()
方法,返回一个适合自己数据结构的具体迭代器对象。
以ArrayList
为例,它的iterator()
方法会返回一个Itr
内部类的实例,这个Itr
类就是具体迭代器,它实现了Iterator
接口,并重写了hasNext()
、next()
、remove()
等方法,实现了对ArrayList
元素的遍历和删除操作。
三、Java 集合框架中的迭代器实现
(一)Iterator 接口的核心方法
Java 的Iterator
接口定义了遍历集合的基本操作,其源码如下:
java
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
hasNext()
方法用于判断是否还有下一个元素,返回布尔值next()
方法返回下一个元素,并将迭代器的位置后移remove()
方法用于删除迭代器最后一次返回的元素,这是一个可选操作,默认实现会抛出异常forEachRemaining()
方法是 Java 8 新增的默认方法,用于对剩余元素执行指定的消费动作
(二)Iterable 接口与 foreach 循环
Iterable
接口是 Java 集合框架中另一个重要的接口,其定义如下:
java
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
任何实现了Iterable
接口的类都可以使用foreach
循环,因为foreach
循环的底层实现就是通过调用iterator()
方法获取迭代器,然后通过hasNext()
和next()
方法来遍历元素。例如,当我们对一个List
集合使用foreach
循环时,实际上编译器会将其转换为迭代器的遍历方式。
(三)以 ArrayList 为例解析迭代器实现
ArrayList
的迭代器实现是一个典型的具体迭代器,其内部类Itr
实现了Iterator
接口:
java
private class Itr implements Iterator<E> {
int cursor; // 下一个元素的索引
int lastRet = -1; // 上一个返回元素的索引,-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];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
这个迭代器实现维护了三个关键变量:
cursor
:表示下一个要访问的元素的索引lastRet
:表示上一次调用next()
方法返回的元素的索引expectedModCount
:用于检测集合是否被并发修改,确保迭代过程中的一致性
在遍历过程中,如果集合的结构被修改(例如通过add()
或remove()
方法),modCount
的值会发生变化,当迭代器检测到modCount
与expectedModCount
不一致时,就会抛出ConcurrentModificationException
,这就是 fail-fast 机制,用于快速检测并发修改问题。
四、自定义迭代器:实现一个书架遍历功能
(一)定义抽象聚合接口
首先定义一个BookShelf
接口,作为抽象聚合,声明创建迭代器的方法:
java
public interface BookShelf extends Iterable<Book> {
void addBook(Book book);
Book getBookAt(int index);
int getBookCount();
}
这里让BookShelf
继承Iterable
接口,这样它就可以使用foreach
循环,同时定义了添加书籍、获取指定索引书籍和获取书籍数量的方法。
(二)实现具体聚合类
具体聚合类ArrayBookShelf
使用数组来存储书籍,并实现iterator()
方法返回具体迭代器:
java
public class ArrayBookShelf implements BookShelf {
private Book[] books;
private int last = 0;
public ArrayBookShelf(int capacity) {
this.books = new Book[capacity];
}
@Override
public void addBook(Book book) {
books[last] = book;
last++;
}
@Override
public Book getBookAt(int index) {
return books[index];
}
@Override
public int getBookCount() {
return last;
}
@Override
public Iterator<Book> iterator() {
return new BookShelfIterator(this);
}
}
iterator()
方法返回一个BookShelfIterator
实例,这是我们自定义的具体迭代器。
(三)实现具体迭代器
java
public class BookShelfIterator implements Iterator<Book> {
private BookShelf bookShelf;
private int index;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < bookShelf.getBookCount();
}
@Override
public Book next() {
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
@Override
public void remove() {
// 可选实现,这里简单抛出异常
throw new UnsupportedOperationException("remove operation is not supported");
}
}
这个迭代器维护了一个指向BookShelf
的引用和当前索引index
,通过hasNext()
和next()
方法实现对书架中书籍的顺序遍历。
(四)客户端使用示例
java
public class Client {
public static void main(String[] args) {
BookShelf shelf = new ArrayBookShelf(3);
shelf.addBook(new Book("Java编程思想"));
shelf.addBook(new Book("设计模式"));
shelf.addBook(new Book("算法导论"));
// 使用迭代器遍历
Iterator<Book> iterator = shelf.iterator();
while (iterator.hasNext()) {
Book book = iterator.next();
System.out.println(book.getName());
}
// 使用foreach循环遍历
for (Book book : shelf) {
System.out.println(book.getName());
}
}
}
通过自定义迭代器,我们实现了对书架中书籍的遍历功能,客户端代码无需关心书架的内部存储结构(数组或其他数据结构),只需要通过迭代器接口即可完成遍历操作。
五、迭代器模式的使用场景
(一)需要为不同集合提供统一遍历接口时
当系统中存在多种不同类型的集合(如数组、链表、树、图等),且需要为客户端提供统一的遍历方式时,迭代器模式可以让客户端代码以相同的方式处理不同的集合,提高代码的通用性和可维护性。例如,Java 集合框架中的Iterator
接口就是一个统一的遍历接口,客户端代码可以用同样的方式遍历List
、Set
、Map
等不同类型的集合(虽然Map
需要通过entrySet()
等方法获取迭代器)。
(二)需要分离集合的遍历逻辑时
当集合类的内部结构比较复杂,或者需要支持多种遍历方式(如正向遍历、反向遍历、按某种规则过滤遍历等)时,将遍历逻辑封装在迭代器中可以避免集合类变得臃肿。例如,LinkedList
可以提供正向迭代器和反向迭代器,通过不同的迭代器实现来支持不同的遍历需求。
(三)需要隐藏集合内部表示时
迭代器模式可以让客户端代码只通过迭代器接口来访问集合元素,而无需了解集合的内部结构,这样可以提高数据的封装性,保护集合的内部数据不被客户端直接修改。例如,我们自定义的ArrayBookShelf
类,客户端只能通过迭代器来遍历书籍,而无法直接访问存储书籍的数组。
(四)需要支持并行遍历或惰性遍历场景
在一些需要延迟加载元素或者支持并行遍历的场景中,迭代器模式也非常适用。例如,Java 8 引入的Spliterator
(分割迭代器)就是为了支持并行遍历集合元素,以配合 Stream API 进行并行处理,而Spliterator
也是迭代器模式的一种扩展应用。
六、迭代器模式的优缺点分析
(一)主要优点
- 职责分离:将集合的存储职责和遍历职责分离,符合单一职责原则,使得集合类和迭代器类的职责更加清晰。
- 统一接口:为不同的集合类提供了统一的遍历接口,客户端代码可以以相同的方式处理不同的集合,提高了代码的通用性和可复用性。
- 封装遍历逻辑:将具体的遍历逻辑封装在迭代器类中,集合类只需提供数据存储功能,无需关心遍历细节,方便后续添加新的遍历方式。
- 支持复杂遍历:可以很容易地实现各种复杂的遍历算法,只需创建新的迭代器类即可,无需修改集合类和客户端代码。
(二)潜在缺点
- 增加类的数量:迭代器模式会引入抽象迭代器、具体迭代器、抽象聚合、具体聚合等多个类,对于简单的遍历场景可能会显得过于复杂,增加了系统的类数量。
- 依赖关系:迭代器通常需要持有对聚合对象的引用,这在一定程度上增加了两者之间的耦合度,虽然这种耦合是合理且必要的,但在某些极端情况下可能会影响性能。
- 功能限制 :标准的迭代器接口(如 Java 的
Iterator
)通常只支持向前遍历,如果需要支持反向遍历或随机访问,可能需要扩展迭代器接口或实现特定的迭代器类。
七、与其他设计模式的关联
(一)工厂模式
在迭代器模式中,聚合对象通常通过工厂方法(如iterator()
方法)来创建迭代器对象,这可以看作是工厂模式的一种简单应用。聚合对象作为工厂,负责创建对应的迭代器实例。
(二)组合模式
组合模式通常用于处理树形结构的对象集合,而迭代器模式可以为组合模式中的组合对象提供统一的遍历接口。例如,一个组合对象(如目录)可以返回一个迭代器,该迭代器能够遍历其子对象(文件和子目录),无论子对象是叶子节点还是组合节点。
(三)装饰器模式
迭代器本身可以作为装饰的目标,通过装饰器模式为迭代器添加额外的功能,如过滤、排序等。例如,Java 集合框架中的Iterator
可以通过包装类(如Collections
类中的静态方法)来添加额外的功能,这实际上就是装饰器模式的应用。
(四)迭代器模式 vs 枚举
在 Java 中,枚举(Enumeration)也曾用于遍历集合,但其功能相对有限(没有remove
方法,且是 Java 1.0 的遗留接口)。迭代器模式相比枚举更加灵活和强大,支持元素的删除操作,并且是 Java 集合框架的标准组成部分。
八、最佳实践与注意事项
(一)遵循接口隔离原则
在定义迭代器接口时,应确保接口简洁,只包含必要的方法。Java 的Iterator
接口就很好地遵循了这一原则,只定义了遍历所需的核心方法,而将可选操作(如remove
)提供默认实现,避免客户端被迫实现不需要的方法。
(二)处理并发修改问题
如前面ArrayList
迭代器的实现所示,通过维护expectedModCount
来检测集合的结构修改,这是处理并发修改的常见做法。在自定义迭代器时,如果需要支持快速失败(fail-fast)机制,也可以采用类似的方式。
(三)考虑线程安全
如果迭代器需要在多线程环境下使用,需要考虑线程安全问题。可以通过同步控制(如synchronized
关键字)或者使用线程安全的集合类(如ConcurrentHashMap
)来保证迭代过程的线程安全。
(四)合理设计迭代器的生命周期
迭代器通常与创建它的聚合对象绑定,当聚合对象被销毁时,迭代器也应随之失效。此外,在使用迭代器时,应避免长时间持有迭代器对象,尤其是在集合可能被频繁修改的情况下,以防止出现并发修改异常。
九、总结
迭代器模式是 Java 集合框架中不可或缺的一部分,它通过分离集合的遍历逻辑,提供了一种优雅、统一的方式来访问集合元素。掌握迭代器模式不仅能够让我们更好地理解和使用 Java 集合框架,还能在自定义数据结构时设计出更灵活、可扩展的遍历方案。
从设计原则的角度来看,迭代器模式完美体现了单一职责原则和依赖倒置原则,它将遍历算法封装在迭代器中,使得集合类和迭代器类可以独立演化。无论是处理简单的线性集合,还是复杂的树形结构,迭代器模式都能为我们提供清晰的解决方案。
在实际开发中,我们应该充分利用 Java 内置的迭代器接口(如Iterator
和Iterable
),当内置功能无法满足需求时,通过自定义迭代器来扩展遍历功能。同时,要注意处理并发修改、线程安全等问题,确保迭代器的实现健壮可靠。
随着 Java 语言的不断发展,迭代器模式也在不断演进,从早期的Iterator
接口到 Java 8 的forEachRemaining
方法,再到 Java 9 的try-with-resources
对Iterator
的支持(通过实现AutoCloseable
),迭代器模式始终在适应新的编程范式和需求。理解其核心思想,将有助于我们在面对各种集合遍历问题时,做出更加优雅和高效的设计选择。