设计模式-迭代器模式
迭代器模式
一、核心思想(一句话概括)
迭代器模式提供了一种顺序访问一个聚合对象(如列表、集合)中各个元素的方法,而又无需暴露该对象的内部表示。
简单来说,它的核心就是 “分离集合对象的遍历行为”。集合对象(Collection)只管存东西,而遍历(Traversal)的责任则交给迭代器(Iterator)。
二、一个生动的比喻:电视遥控器
想象一下你家里的各种设备:电视机、蓝光播放器、音响。
-
聚合对象(Collection):就是这些设备本身(电视机、播放器等)。它们内部的构造非常复杂(电路板、芯片、显像管...)。
-
遍历操作:就是“切换到下一个频道”或“播放下一首歌曲”。
如果没有迭代器模式: 你要换台,可能需要打开电视机后盖,找到调谐器,手动拨动一个旋钮。换歌,则需要操作蓝光播放器的激光头。你需要了解每种设备内部的实现细节才能操作它。这显然是荒谬的。
有了迭代器模式: 我们发明了遥控器(Iterator)。遥控器上有一个统一的接口,比如 “下一个(+)” 和 “上一个(-)” 按钮。
-
你用电视遥控器的 “下一个(+)” 按钮,电视就切换到下一个频道。
-
你用音响遥控器的 “下一个(+)” 按钮,音响就播放下一首歌曲。
你只需要学会使用遥控器(统一的遍历接口),而完全不需要关心电视机和音响的内部是如何实现“下一个”这个功能的。遥控器为你屏蔽了底层实现的复杂性。
这就是迭代器模式的精髓:为遍历提供一个统一的接口,让客户端与集合的具体实现解耦。
三、要解决的问题
在软件开发中,我们有各种各样的数据集合:
-
ArrayList:底层是数组,通过索引 i++ 遍历最快。
-
LinkedList:底层是链表,通过 node = node.next 遍历最高效。如果用索引遍历会非常慢。
-
HashSet:底层是哈希表,根本没有索引的概念,遍历顺序也不确定。
如果我们的业务代码直接依赖这些集合的具体实现来写遍历循环,会怎么样?
// 客户端代码 public void printElements(ArrayListlist) { for (int i = 0; i < list.size(); i++) { // 依赖 ArrayList 的索引 System.out.println(list.get(i)); } } public void printElements(LinkedList list) { // 糟糕,如果换成 LinkedList,上面的代码效率极低 // 我必须为 LinkedList 重写一个版本 }
这会导致:
-
暴露内部细节:客户端代码被迫知道了 ArrayList 是基于索引的。
-
代码缺乏通用性:如果把 ArrayList 换成 LinkedList 或 HashSet,遍历代码就要重写。这违反了“开闭原则”。
迭代器模式就是来解决这个问题的。
四、迭代器模式的结构与实现
它主要包含四个角色:
-
Iterator(迭代器接口):定义了遍历所需的基本方法,最核心的是:
-
hasNext(): 判断是否还有下一个元素。
-
next(): 返回下一个元素,并将指针后移。
-
remove(): (可选) 删除当前元素。
-
-
ConcreteIterator(具体迭代器):实现 Iterator 接口,负责对特定的聚合对象进行遍历。它内部需要维持一个遍历的状态(比如当前索引、当前节点引用)。
-
Aggregate(聚合接口):定义了集合应该具备的方法,其中最核心的是 iterator(),它负责返回一个 Iterator 对象。
-
ConcreteAggregate(具体聚合):实现 Aggregate 接口,是具体的集合类。它实现了 iterator() 方法,返回一个与自己相匹配的 ConcreteIterator 实例。
代码实现(以一个自定义的“书架”为例)
第1步:定义 Iterator 和 Aggregate 接口
// Iterator 接口 public interface Iterator{ boolean hasNext(); E next(); } // Aggregate 接口 public interface Aggregate { Iterator iterator(); }
第2步:创建具体聚合类 (ConcreteAggregate)
// 具体的集合:书架 public class BookShelf implements Aggregate{ private Book[] books; private int last = 0; // 当前书的数量 public BookShelf(int maxSize) { this.books = new Book[maxSize]; } public Book getBookAt(int index) { return books[index]; } public void addBook(Book book) { if (last < books.length) { this.books[last] = book; last++; } } public int getLength() { return last; } // 核心:返回一个为 BookShelf 服务的迭代器实例 @Override public Iterator iterator() { return new BookShelfIterator(this); } }
第3步:创建具体迭代器类 (ConcreteIterator)
// 为 BookShelf 服务的具体迭代器 public class BookShelfIterator implements Iterator{ private BookShelf bookShelf; private int index; public BookShelfIterator(BookShelf bookShelf) { this.bookShelf = bookShelf; this.index = 0; } @Override public boolean hasNext() { // 判断索引是否超出了书架上书的总数 return index < bookShelf.getLength(); } @Override public Book next() { if (!hasNext()) { throw new java.util.NoSuchElementException(); } Book book = bookShelf.getBookAt(index); index++; return book; } } // 书的实体类(略) class Book { private String name; public Book(String name) {this.name = name;} public String getName() {return name;} }
第4步:客户端使用
public class Client { public static void main(String[] args) { BookShelf bookShelf = new BookShelf(4); bookShelf.addBook(new Book("设计模式")); bookShelf.addBook(new Book("Java编程思想")); bookShelf.addBook(new Book("代码整洁之道")); bookShelf.addBook(new Book("深入理解Java虚拟机")); // 客户端通过统一的 Iterator 接口来遍历,完全不关心 BookShelf 内部是数组还是别的 Iteratorit = bookShelf.iterator(); while (it.hasNext()) { Book book = it.next(); System.out.println(book.getName()); } } }
看,客户端代码多么干净!它只依赖于 Iterator 接口,完全不关心 BookShelf 是怎么存书的。
五、Java 中的迭代器模式
你其实每天都在使用它!Java 的集合框架(JCF)就是迭代器模式的完美实践。
-
java.util.Iterator 就是我们的 Iterator 接口。
-
java.lang.Iterable 就是我们的 Aggregate 接口。它只有一个方法 iterator()。
-
所有 Java 集合类(ArrayList, LinkedList, HashSet等)都实现了 Iterable 接口,它们就是 ConcreteAggregate。
-
每个集合类都有一个内部类来实现 Iterator 接口,比如 ArrayList 有 Itr,LinkedList 有 ListItr,它们就是 ConcreteIterator。
这就是为什么我们可以对所有 Java 集合使用 for-each 循环:
Listlist = new ArrayList<>(); // ... add elements // for-each 循环是迭代器模式的语法糖! for (String item : list) { System.out.println(item); }
编译器会自动将上面的 for-each 循环转换成下面这样基于迭代器的代码:
Iteratoriterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); System.out.println(item); }
六、优缺点
优点
-
封装性好:将遍历逻辑从集合中分离出来,客户端无需知道集合的内部结构。
-
单一职责原则:集合负责存储,迭代器负责遍历,职责清晰。
-
通用性强:为所有集合提供了统一的遍历接口,简化了客户端代码。
-
支持多种遍历:可以对同一个集合创建多个迭代器,每个迭代器都独立地维护自己的遍历状态。
缺点
1. 结构复杂性与类的增加
这是我之前提到的那点,但可以更深入一些。对于每一种聚合(集合)类型,理论上都需要一个与之对应的具体迭代器类。
-
简单场景下的过度设计:如果你的系统非常简单,只有一个 ArrayList,而且未来也不太可能改变,那么专门为其设计接口和迭代器类,确实会比直接用 for (int i=0; ...) 循环显得“重”。
-
平行的类层次结构:你需要在系统中维护两个平行的类层次结构:一个是聚合类的层次结构(List, Set 等),另一个是迭代器类的层次结构(ArrayListIterator, HashSetIterator 等)。这增加了系统的整体复杂性。
2. 并发修改的限制(非常重要!)
这是迭代器模式在实际应用中最常遇到的一个“坑”,尤其是在多线程环境下。
Java 集合框架中的绝大多数迭代器都是快速失败(Fail-Fast)的。
-
什么是“快速失败”? 当你在使用一个迭代器遍历集合时,如果有另一个线程(或者甚至是当前线程通过集合本身的方法而不是迭代器的方法)修改了集合的结构(比如添加、删除元素),那么这个迭代器会立刻抛出 ConcurrentModificationException 异常。
-
为什么会这样? ArrayList 等集合内部有一个计数器 modCount。每当集合的结构被修改(调用 add, remove 等方法),modCount 就会加一。 当你创建迭代器时,迭代器会记下当时的 modCount 值。在每次调用迭代器的 next() 方法时,它都会检查自己记录的 modCount 是否和集合当前的 modCount 一致。如果不一致,就说明集合在遍历期间被外部修改了,迭代器会立即“失败”(抛出异常),以避免在不确定的状态下继续遍历,从而产生无法预料的后果。
-
带来的缺点: 这使得标准的迭代器在多线程共享集合的场景下几乎无法直接使用。你必须采取额外的同步措施(比如使用 synchronized 锁住整个集合),或者使用专门为并发设计的集合类(如 ConcurrentHashMap 或 CopyOnWriteArrayList),它们的迭代器是弱一致性或快照式的,不会抛出此异常。
3. 遍历方式的局限性
标准的 java.util.Iterator 接口功能非常基础,它只支持单向的、向前的遍历。
-
无法后退:你不能通过 Iterator 接口从后往前遍历,或者在元素之间来回移动。
-
需要专门的子接口:为了解决这个问题,Java 提供了 ListIterator 接口(专用于 List),它继承了 Iterator 并增加了 hasPrevious(), previous(), add() 等方法,允许双向遍历和在遍历时修改列表。这说明基础的迭代器模式本身功能有限,需要扩展来满足更复杂的需求。
4. remove() 方法的复杂性与可选性
Iterator 接口中的 remove() 方法给使用者带来了一些心智负担。
-
可选实现:remove() 是一个“可选操作”。这意味着一个迭代器完全可以不支持删除功能。如果调用一个不支持删除的迭代器的 remove() 方法,它会抛出 UnsupportedOperationException。这就要求使用者在使用前必须清楚该迭代器是否支持此操作。
-
状态依赖:remove() 的调用是有状态的。它必须在调用 next() 之后、并且在下一次调用 next() 之前被调用。如果你连续调用两次 remove(),或者在没有调用 next() 的情况下就调用 remove(),都会抛出 IllegalStateException。这种严格的调用顺序要求开发者必须小心翼翼。
5. 轻微的性能开销
在对性能要求极其苛刻的场景下,迭代器模式会引入一些微小的开销。
-
对象创建:每次调用 iterator() 都会创建一个新的迭代器对象,这有微小的内存和CPU开销。
-
方法调用:通过 hasNext() 和 next() 进行的调用是方法调用,相比于直接在 for 循环中通过索引访问数组元素(array[i]),开销会略大一些。
但是,必须强调:对于 99.9% 的应用程序来说,这点性能差异完全可以忽略不计。通过迭代器模式换来的代码解耦、可维护性和安全性的收益,远远超过这点微不足道的性能损失。过早地为此进行优化是典型的大忌。