Java案例中的迭代器模式怎么用?

wen java案例 2

深入解析Java迭代器模式:从理论到实战案例的完整指南

目录导读

  • 什么是迭代器模式? —— 核心定义与设计初衷
  • 为什么需要迭代器模式? —— 解决集合遍历的耦合问题
  • Java中的迭代器模式实现 —— Iterator接口与具体实现
  • 经典案例1:自定义集合类的迭代器实现
  • 经典案例2:树形结构的中序遍历迭代器
  • 经典案例3:数据库结果集的游标式迭代
  • 迭代器模式与增强for循环的关系
  • 常见问题QA —— 开发中高频疑问解答
  • 迭代器模式在JDK源码中的应用
  • 最佳实践与性能优化建议

什么是迭代器模式?

迭代器模式(Iterator Pattern) 是一种行为型设计模式,它提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。

Java案例中的迭代器模式怎么用?

迭代器模式将“遍历”这个行为从集合对象中抽离出来,封装成一个独立的迭代器对象,这样,无论底层集合是数组、链表、树还是图,调用者都可以用统一的方式访问元素。

核心角色

角色 说明 Java示例
Iterator(迭代器接口) 定义hasNext()、next()等方法 java.util.Iterator
ConcreteIterator(具体迭代器) 实现迭代逻辑,维护遍历位置 ArrayList的内部Itr类
Aggregate(聚合接口) 定义创建迭代器的方法 java.util.Collection
ConcreteAggregate(具体聚合) 返回一个具体的迭代器实例 ArrayList、HashSet

为什么需要迭代器模式?

假设你有一个列表,需要遍历所有元素,传统做法是直接使用for循环下标访问:

for (int i = 0; i < list.size(); i++) {
    Item item = list.get(i);
    // 处理item
}

但如果你把列表换成树或Set呢?代码必须重写,迭代器模式的核心价值在于:

  1. 解耦遍历算法与集合结构 —— 集合改变时,遍历代码无需改动
  2. 统一遍历接口 —— 无论什么集合,都是hasNext()+next()
  3. 支持多种遍历方式 —— 同一个集合可以有正序、逆序、条件过滤等多种迭代器
  4. 延迟遍历 —— 按需访问,不需要一次性加载所有元素(对大数据量特别重要)

Java中的迭代器模式实现

Java从1.2版本开始便内置了迭代器模式,核心在 java.util.Iterator 接口中:

public interface Iterator<E> {
    boolean hasNext();    // 是否还有下一个元素
    E next();             // 返回下一个元素
    default void remove() { throw new UnsupportedOperationException(); }
    // Java 8 新增的forEachRemaining
    default void forEachRemaining(Consumer<? super E> action) { ... }
}

所有集合类(ArrayList、HashSet、LinkedList等)都实现了 Iterable 接口,该接口返回一个Iterator实例:

public interface Iterable<T> {
    Iterator<T> iterator();
}

这意味着任何集合都可以通过 for (T item : collection) 语法遍历——这正是迭代器模式在Java中最广泛的应用。


经典案例1:自定义集合类的迭代器实现

假设我们有一个 BookShelf(书架)类,内部用数组存放书籍,但我们希望客户端能像遍历ArrayList一样遍历它。

步骤1:定义聚合接口

public interface Aggregate {
    Iterator<Book> iterator();
}

步骤2:定义书籍类

public class Book {
    private String name;
    public Book(String name) { this.name = name; }
    public String getName() { return name; }
}

步骤3:实现具体聚合类

public class BookShelf implements Aggregate {
    private Book[] books;
    private int last = 0;
    public BookShelf(int maxSize) {
        this.books = new Book[maxSize];
    }
    public void appendBook(Book book) {
        this.books[last] = book;
        last++;
    }
    public int getLength() { return last; }
    @Override
    public Iterator<Book> iterator() {
        return new BookShelfIterator(this);
    }
}

步骤4:实现具体迭代器

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.getLength();
    }
    @Override
    public Book next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        return bookShelf.getBookAt(index++);
    }
}

步骤5:使用迭代器

public class Main {
    public static void main(String[] args) {
        BookShelf shelf = new BookShelf(5);
        shelf.appendBook(new Book("Design Patterns"));
        shelf.appendBook(new Book("Clean Code"));
        shelf.appendBook(new Book("Effective Java"));
        // 使用迭代器遍历
        Iterator<Book> it = shelf.iterator();
        while (it.hasNext()) {
            Book book = it.next();
            System.out.println(book.getName());
        }
        // 也支持增强for循环
        for (Book book : shelf) {
            System.out.println(book.getName());
        }
    }
}

关键点:客户端完全不知道 BookShelf 内部是用数组存储的,如果后续改成 ArrayListLinkedList,只要迭代器实现正确,客户端代码无需任何修改。


经典案例2:树形结构的中序遍历迭代器

迭代器模式最强大的应用之一是对非线性结构的遍历,例如二叉树的中序遍历,我们可以实现一个 InOrderIterator

public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}
public class InOrderIterator implements Iterator<Integer> {
    private Stack<TreeNode> stack = new Stack<>();
    private TreeNode current;
    public InOrderIterator(TreeNode root) {
        this.current = root;
        // 先一路向左压栈
        while (current != null) {
            stack.push(current);
            current = current.left;
        }
    }
    @Override
    public boolean hasNext() {
        return !stack.isEmpty();
    }
    @Override
    public Integer next() {
        if (!hasNext()) throw new NoSuchElementException();
        TreeNode node = stack.pop();
        int result = node.val;
        // 如果有右子树,压入右子树及其整条左链
        if (node.right != null) {
            current = node.right;
            while (current != null) {
                stack.push(current);
                current = current.left;
            }
        }
        return result;
    }
}

使用场景:在处理XML/JSON解析、文件系统遍历、游戏场景管理时,这种基于状态的迭代器非常有价值。


经典案例3:数据库结果集的游标式迭代

迭代器模式也常用于数据库访问层,例如模拟一个简单的结果集游标:

public class ResultSetIterator implements Iterator<Map<String, Object>> {
    private final ResultSet resultSet;
    private boolean hasNext = false;
    private boolean moved = false;
    public ResultSetIterator(ResultSet rs) {
        this.resultSet = rs;
    }
    @Override
    public boolean hasNext() {
        if (!moved) {
            try {
                hasNext = resultSet.next();
                moved = true;
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        return hasNext;
    }
    @Override
    public Map<String, Object> next() {
        if (!hasNext()) throw new NoSuchElementException();
        moved = false;
        // 将当前行转换为Map
        Map<String, Object> row = new HashMap<>();
        try {
            ResultSetMetaData meta = resultSet.getMetaData();
            for (int i = 1; i <= meta.getColumnCount(); i++) {
                row.put(meta.getColumnName(i), resultSet.getObject(i));
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return row;
    }
}

这样我们便可以用统一的方式处理数据库记录,甚至可以和Stream API结合:

ResultSetIterator it = new ResultSetIterator(rs);
StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, 0), false)
             .filter(row -> "Active".equals(row.get("status")))
             .forEach(row -> System.out.println(row));

优势:数据库游标天然就是一个迭代器设计,逐条获取、按需加载,而不是一次性将所有数据加载到内存。


迭代器模式与增强for循环的关系

Java的增强for循环(for-each)本质上是语法糖,编译器会将其转换为迭代器遍历:

// 源代码
for (String s : list) {
    System.out.println(s);
}
// 编译后等价于
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    System.out.println(s);
}

重要提醒:在增强for循环中不能直接修改集合(add/remove),因为迭代器会检测到结构性修改并抛出 ConcurrentModificationException,如果需要遍历时删除,必须显式使用迭代器的 remove() 方法。


常见问题QA

Q1:迭代器模式与for循环,哪个性能更好?

A:对于ArrayList这类随机访问集合,传统for循环(下标索引)略快于迭代器(约5%-10%),但对于LinkedList,迭代器比下标索引快数十倍,因为下标索引每次都要从头遍历。在不确定集合类型时,迭代器是更安全的选择

Q2:为什么迭代器的remove()方法现在被标记为“废弃倾向”?

A:因为Java 8引入了Collection.removeIf()方法,它更清晰且支持Lambda,remove()方法要求开发者在调用next()之后立即调用remove(),顺序错误会抛异常,容易出错,推荐优先使用 collection.removeIf(predicate)

Q3:如何实现一个只读的迭代器?

A:在next()方法中返回集合元素的不可变副本(如使用Collections.unmodifiableList),或者在迭代器内部禁用remove()方法,直接抛出UnsupportedOperationException。

Q4:迭代器可以支持倒序遍历吗?

A:可以,只需要在迭代器中维护一个倒序的索引或使用双向链表,并实现hasPrevious()和previous()方法,Java的ListIterator正是这种双向迭代器。

Q5:大数据的迭代器有什么注意事项?

A:迭代器适合“按需加载”场景,但要注意:

  • 不要在多线程环境下共享同一个迭代器,除非显式做同步
  • 迭代器内部如果持有数据库连接或文件句柄,务必在finally块中关闭
  • 可考虑使用回退迭代器(PeekingIterator)来提前查看下一个元素

迭代器模式在JDK源码中的应用

除了常见的集合类,JDK中还有很多迭代器模式的典范:

  1. java.util.Scanner:通过迭代器的 hasNext()/next() 方法来解析输入流
  2. java.nio.file.DirectoryStream:返回目录下的文件列表,支持迭代器遍历
  3. java.util.ServiceLoader:加载服务提供者列表,本质也是迭代器模式
  4. Stream API的内部迭代:Stream的 iterator() 方法返回一个 Spliterator,它是并行迭代的迭代器

Spliterator 是Java 8引入的增强版迭代器,支持分区遍历和并行处理,是JVM进行流优化的重要基础。


最佳实践与性能优化建议

  1. 选择适当的迭代器类型

    • 如果只需要顺序遍历,用Iterator
    • 如果需要前后遍历或修改元素,用ListIterator
    • 如果需要并行处理大数据集,用Spliterator
  2. 避免在迭代器内部持有过多状态

    迭代器对象可能在多线程环境下被短暂使用,尽量减少内部缓存或预加载

  3. 使用不可变迭代器确保线程安全

    • Collections.unmodifiableCollection(collection).iterator() 返回的迭代器不允许修改操作,适合作为API返回值
  4. 组合迭代器模式与工厂模式

    • 如果你想为同一个集合提供正序、逆序、过滤等多种遍历方式,可以定义一个 IteratorFactory 来生成不同的迭代器实例
  5. 避免重复创建迭代器对象

    如果集合内容没有变化,但需要多次遍历,可以考虑缓存迭代器(复制一份可重用的)——注意线程安全

抱歉,评论功能暂时关闭!