Skip to content

迭代器模式(Iterator Pattern) ​

一句话记忆口诀:迭代器统一遍历接口,hasNext + next 是核心,for-each 语法糖底层就是迭代器。

1. 🏠 生活类比 ​

电视遥控器的"下一个频道"按钮:无论电视内部是用数组、链表还是哈希表存储频道列表,用户只需要按"下一个"就能遍历所有频道。用户不需要知道频道是怎么存储的。

图书馆的索引卡片:无论书架上的书怎么排列,索引卡片提供了一种统一的方式来逐本查找。

2. 💩 烂代码:遍历方式与集合类型强耦合 ​

java
// ❌ 反例:每种集合都要用不同的遍历方式
// 数组遍历
String[] array = {"a", "b", "c"};
for (int i = 0; i < array.length; i++) {
    System.out.println(array[i]);
}

// 链表遍历
LinkedList<String> list = new LinkedList<>();
for (Node node = list.head; node != null; node = node.next) {
    System.out.println(node.value);
}

// 二叉树遍历(中序)
void inorder(TreeNode node) {
    if (node == null) return;
    inorder(node.left);
    System.out.println(node.value);
    inorder(node.right);
}

// 如果集合类型从数组换成链表,遍历代码全部要改!
// 客户端必须了解每种集合的内部结构!

问题根因:不同集合有不同的内部结构和遍历方式,客户端与集合的具体实现强耦合。

3. ✨ 迭代器模式方案 ​

4. 💻 完整代码实现 ​

java
import java.util.*;
import java.util.stream.StreamSupport;

// ===== 自定义集合:书架 =====
public class BookShelf implements Iterable<Book> {
    private final List<Book> books = new ArrayList<>();

    public void addBook(Book book) {
        books.add(book);
    }

    public Book getBookAt(int index) {
        return books.get(index);
    }

    public int size() {
        return books.size();
    }

    @Override
    public Iterator<Book> iterator() {
        return new BookIterator(this);
    }
}

// ===== 书籍 =====
public class Book {
    private final String name;
    private final String author;

    public Book(String name, String author) {
        this.name = name;
        this.author = author;
    }

    public String getName() { return name; }
    public String getAuthor() { return author; }

    @Override
    public String toString() {
        return "《" + name + "》- " + author;
    }
}

// ===== 具体迭代器 =====
public class BookIterator implements Iterator<Book> {
    private final BookShelf shelf;
    private int index = 0;

    public BookIterator(BookShelf shelf) {
        this.shelf = shelf;
    }

    @Override
    public boolean hasNext() {
        return index < shelf.size();
    }

    @Override
    public Book next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        Book book = shelf.getBookAt(index);
        index++;
        return book;
    }
}

// ===== 使用示例 =====
public class Main {
    public static void main(String[] args) {
        BookShelf shelf = new BookShelf();
        shelf.addBook(new Book("设计模式", "GoF"));
        shelf.addBook(new Book("重构", "Martin Fowler"));
        shelf.addBook(new Book("Effective Java", "Joshua Bloch"));

        // 方式一:显式使用迭代器
        Iterator<Book> it = shelf.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

        // 方式二:for-each 语法糖(底层就是迭代器)
        for (Book book : shelf) {
            System.out.println(book);
        }

        // 方式三:Stream(内部迭代,Iterable 需经 StreamSupport 转流)
        StreamSupport.stream(shelf.spliterator(), false)
             .filter(b -> b.getName().contains("设计"))
             .forEach(System.out::println);
    }
}

4.1 进阶示例:二叉树支持多种遍历方式(中序实现示例) ​

java
// ===== 二叉树支持前序、中序、后序遍历 =====
public class BinaryTree<T> implements Iterable<T> {
    private TreeNode<T> root;
    private TraversalOrder order = TraversalOrder.INORDER;

    public enum TraversalOrder { PREORDER, INORDER, POSTORDER }

    public void setOrder(TraversalOrder order) { this.order = order; }

    @Override
    public Iterator<T> iterator() {
        return switch (order) {
            case PREORDER -> new PreorderIterator<>(root);
            case INORDER -> new InorderIterator<>(root);
            case POSTORDER -> new PostorderIterator<>(root);
        };
    }

    // PreorderIterator 和 PostorderIterator 的实现从略,此处仅展示不同遍历顺序对应的迭代器选择。

    // 中序迭代器实现
    private static class InorderIterator<T> implements Iterator<T> {
        private final Stack<TreeNode<T>> stack = new Stack<>();

        public InorderIterator(TreeNode<T> root) {
            pushLeft(root);
        }

        private void pushLeft(TreeNode<T> node) {
            while (node != null) {
                stack.push(node);
                node = node.left;
            }
        }

        @Override
        public boolean hasNext() { return !stack.isEmpty(); }

        @Override
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            TreeNode<T> node = stack.pop();
            pushLeft(node.right);
            return node.value;
        }
    }
}

class TreeNode<T> {
    T value;
    TreeNode<T> left, right;
    TreeNode(T value) { this.value = value; }
}

5. 🔧 框架应用 ​

框架/类说明
java.util.IteratorJDK 标准迭代器接口
Iterable + for-each语法糖,编译后使用 Iterator
java.util.stream.StreamJava 8 内部迭代器,支持惰性求值
ListIterator双向迭代器,支持向前/向后遍历
EnumerationJDK 1.0 的迭代器(已被 Iterator 替代)
MyBatis Cursor大数据量查询的流式迭代器
ResultSetJDBC 结果集遍历

6. ⚠️ 适用场景 ​

适合:

  • 需要统一遍历不同类型的集合
  • 不想暴露集合的内部结构
  • 需要多种遍历方式(前序/中序/后序)
  • 需要惰性遍历(迭代器按 next 逐个取元素,可配合 MyBatis Cursor 流式读取大数据量)

不适合:

  • 集合结构简单,直接 for 循环即可
  • 需要按索引频繁随机访问(Iterator 接口没有随机访问方法,单靠 Iterator 不合适)

7. 🔍 迭代器的 fail-fast vs fail-safe ​

类型代表特点
fail-fastArrayList.iterator()遍历中修改集合 → 可能抛 ConcurrentModificationException
fail-safeCopyOnWriteArrayList.iterator()遍历中修改集合 → 不抛异常(遍历的是快照)

说明:fail-safe 是常见非正式称呼,并非 Java 集合框架的正式分类。ArrayList 迭代器通过内部修改次数检查「尽力检测」结构性修改,但不保证一定抛出异常;上面的异常发生在同一线程的遍历过程中,说明 fail-fast 并不只针对并发场景。CopyOnWriteArrayList 的迭代器遍历的是创建迭代器时的快照,因此看不到之后的更新,代价是每次写操作都要复制整个数组,写多读少时开销较大。

java
// fail-fast 示例
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
    if ("a".equals(s)) {
        list.remove(s); // 当前 ArrayList 实现会在后续迭代时抛出 CME
    }
}

// 正确做法:使用迭代器的 remove 方法
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if ("b".equals(it.next())) it.remove(); // ✅ 安全删除
}

Released under the MIT License.