← 返回题目列表

如何手写一个迭代器模式?Java 代码怎么实现?

高频 中等 第 8 / 25 题 更新于 2026/07/28
迭代器模式Java代码实现

简化版

手写迭代器模式的关键是让集合提供 iterator(),并让迭代器保存当前遍历位置。客户端通过 hasNext()next() 遍历元素,而不是直接访问集合内部数组或节点。

详细版

一个简化实现通常包括:

  • 自定义迭代器接口 MyIterator
  • 自定义集合接口 MyIterable
  • 具体集合类保存元素。
  • 内部迭代器类维护游标。

Java 中也可以直接实现标准的 Iterable<T>Iterator<T>,这样对象就能被增强 for 循环遍历。

完整版教学

一、先定义自定义接口

为了理解模式,可以先写最小版本:

interface MyIterator<E> {
    boolean hasNext();
    E next();
}

interface MyIterable<E> {
    MyIterator<E> iterator();
}

MyIterable 表示“可被遍历”,MyIterator 表示“具体怎么遍历”。

二、实现一个简单集合

class NameList implements MyIterable<String> {
    private final String[] names;

    public NameList(String[] names) {
        this.names = names;
    }

    @Override
    public MyIterator<String> iterator() {
        return new NameIterator();
    }

    private class NameIterator implements MyIterator<String> {
        private int cursor = 0;

        @Override
        public boolean hasNext() {
            return cursor < names.length;
        }

        @Override
        public String next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return names[cursor++];
        }
    }
}

这里 NameIterator 作为内部类,可以访问外部集合的 names,同时自己维护 cursor

三、客户端使用

NameList list = new NameList(new String[]{"Tom", "Jerry", "Alice"});
MyIterator<String> it = list.iterator();

while (it.hasNext()) {
    System.out.println(it.next());
}

客户端不知道 NameList 内部是数组,也不需要知道下标如何变化。

四、实现 Java 标准接口

实际 Java 项目里,更常见的是实现标准接口:

class NameList implements Iterable<String> {
    private final String[] names;

    public NameList(String[] names) {
        this.names = names;
    }

    @Override
    public Iterator<String> iterator() {
        return new Iterator<>() {
            private int cursor = 0;

            @Override
            public boolean hasNext() {
                return cursor < names.length;
            }

            @Override
            public String next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                return names[cursor++];
            }
        };
    }
}

实现 Iterable 后,就可以使用增强 for:

for (String name : new NameList(new String[]{"Tom", "Jerry"})) {
    System.out.println(name);
}

增强 for 底层就是调用 iterator()

五、为什么 next 要抛异常

如果没有下一个元素还调用 next(),Java 标准 Iterator 约定应抛出 NoSuchElementException

这比返回 null 更明确,因为集合中本身可能允许 null 元素。返回 null 会让调用方无法区分“没有元素”和“元素就是 null”。

六、remove 方法要谨慎实现

Java 的 Iterator 还有默认 remove() 方法。它用于删除最近一次 next() 返回的元素。

如果不支持删除,可以抛出 UnsupportedOperationException。不要写一个行为不清楚的空实现,否则调用方以为删除成功,实际数据没有变化。

七、常见误区与追问

实现迭代器要把游标含义、边界和修改策略写清楚。长度为3的数组可令 cursor 表示“下一个返回下标”,初始0,每次 next 后递增,到3时 hasNext 为 false。next() 仍必须自行检查边界,不能假设调用者一定先调用 hasNext。

检查维度判定依据
cursor 初值指向第一个待返回元素
next 后返回当前元素并推进游标
next(): check cursor<3 -> data[cursor++]

易错点:hasNext 是查询,不应偷偷推进游标;推进只能发生在 next。

  • 误区:调用 hasNext 会移动到下一个元素。 标准语义只判断是否存在下一项,可反复调用且结果稳定。
  • 追问:next 为什么要重复边界检查? 调用者可以直接调用 next,迭代器必须自己维护契约。
  • 误区:所有迭代器都必须支持 remove。 可抛 UnsupportedOperationException,但应在 API 中明确。
  • 追问:链表迭代器如何做到 O(1) next? 保存当前节点引用,而不是每次按下标从头查找。
  • 追问:怎样支持 fail-fast? 创建时记录结构版本,每次关键操作比较集合当前版本。

八、加强记忆

手写迭代器模式抓住三件事:集合提供 iterator(),迭代器保存游标,客户端只通过 hasNext()next() 访问元素。能实现 Iterable 时,增强 for 就能自然接入。