Java Iterator要求hasNext()返回false后调用next()必须抛NoSuchElementException,这是强制契约;正确做法是在next()开头校验!hasNext()并抛异常,且统一采用cursor指向下一个待返回元素的语义。

为什么 hasNext() 返回 false 后还调用 next() 会抛异常
Java 的 Iterator 协议要求:一旦 hasNext() 返回 false,后续调用 next() 必须抛出 NoSuchElementException。这不是可选行为,而是接口契约——JDK 自带集合(如 ArrayList)都严格遵循。自定义迭代器若忽略这点,会导致与 for-each 循环不兼容,因为 for (T t : iterable) 底层就是先查 hasNext() 再调 next()。
常见错误是把 next() 写成“无条件返回当前项并移动指针”,没检查是否已越界:
public T next() {
return items[index++]; // ❌ 没判断 index 是否超出范围
}
正确做法是让 next() 依赖 hasNext() 的状态,或在内部重复校验:
-
hasNext()应只做判断,不改变状态 -
next()应先确认可取(可复用hasNext()逻辑或单独判断),再取值、再移动指针 - 建议在
next()开头加if (!hasNext()) throw new NoSuchElementException();,避免逻辑分散
如何安全维护迭代器的内部状态(索引 / 游标 / 当前节点)
状态维护错位是自定义迭代器最常崩的点。比如用数组实现时,index 表示“下一个要返回的元素下标”,那么初始值应为 0;但若用链表节点,current 应指向“当前已返回的节点”,还是“下一个待返回的节点”?不同选择直接影响 hasNext() 和 next() 的边界判断。
立即学习“Java免费学习笔记(深入)”;
推荐统一采用“cursor 指向下一个待返回元素”的语义(和 ArrayList.Itr 一致),好处是逻辑直觉清晰:
- 构造时
cursor = 0 -
hasNext()判断cursor -
next()先保存items[cursor],再执行cursor++ - 这样
cursor始终代表“下次调用next()将取的位置”,不会出现 +1/-1 错位
注意:如果数据源本身可能动态变化(如并发修改),还需考虑 modCount 和快速失败机制,但那是另一层问题——纯单线程场景下,只管好 cursor 的生命周期即可。
泛型擦除下如何让 Iterator<T> 正确返回具体类型
Java 泛型在编译后被擦除,但 Iterator 接口的 next() 方法签名仍必须声明返回 T。如果你写的是 class MyIterator implements Iterator<String>,那 next() 必须返回 String,不能返回 Object 再强转——否则调用方拿到的就是原始类型,泛型约束失效。
常见错误写法:
public Object next() { // ❌ 返回 Object,调用方需手动强转
return items[cursor++];
}
正确写法必须匹配泛型声明:
public String next() { // ✅ 类型精确,编译期就保证安全
if (!hasNext()) throw new NoSuchElementException();
return items[cursor++];
}
关键点:
- 实现类必须明确指定泛型参数,如
MyIterator<Integer> -
next()方法签名必须与泛型一致,不能靠运行时转型补救 - 如果底层存储是
Object[],返回前必须显式转型(如(T) items[cursor++]),但要确保调用方传入的类型安全——这是设计责任,不是语法糖能掩盖的
什么时候该用 Iterable<T> 而不是直接暴露 Iterator<T>
直接返回 Iterator 实例看似简单,但会破坏重用性:一个 Iterator 只能遍历一次,且无法支持多个并发遍历。而实现 Iterable 接口,提供 iterator() 方法,才是标准姿势。
例如:
public class MyList<T> implements Iterable<T> {
private final List<T> data;
public Iterator<T> iterator() {
return new MyIterator<T>(data); // 每次调用都新建干净迭代器
}
}
这样用户才能放心写 for (String s : myList) 或多次调用 myList.iterator()。否则,如果只提供 getIterator() 返回同一个实例,第二次遍历就会从上次中断处继续,甚至抛 IllegalStateException(如果做了 fail-fast 检查)。
容易被忽略的细节:
-
Iterable.iterator()必须每次返回新对象,不能缓存或复用旧实例 - 如果迭代器有外部依赖(如闭包引用了外部变量),确保每次新建时状态隔离
- 别为了省事把
Iterator字段暴露为 public——这等于放弃封装,也违背迭代器“一次性消费”的本意
真要优化性能,应该在迭代器内部做懒计算或缓存,而不是复用实例。










