迭代器模式
问题
什么是迭代器模式?Java 的 Iterator 和 Iterable 有什么区别?
答案
核心思想
提供一种顺序访问集合元素的方法,而不暴露集合的内部结构。客户端通过统一的 Iterator 接口遍历不同的集合类型。
Java 迭代器体系
Iterator 和 Iterable
// Iterable:可迭代的(集合实现此接口)
public interface Iterable<T> {
Iterator<T> iterator(); // 返回迭代器
}
// Iterator:迭代器(遍历的工具)
public interface Iterator<E> {
boolean hasNext(); // 是否还有下一个
E next(); // 获取下一个
void remove(); // 删除当前元素
}
实现了 Iterable 的类可以使用 for-each 循环:
List<String> list = List.of("a", "b", "c");
// for-each 编译后就是 Iterator 调用
for (String s : list) {
System.out.println(s);
}
// 等价于
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
自定义迭代器
自定义可迭代集合
public class NumberRange implements Iterable<Integer> {
private final int start;
private final int end;
public NumberRange(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
private int current = start;
@Override
public boolean hasNext() { return current <= end; }
@Override
public Integer next() {
if (!hasNext()) throw new NoSuchElementException();
return current++;
}
};
}
}
// 使用 for-each
for (int n : new NumberRange(1, 5)) {
System.out.println(n); // 1, 2, 3, 4, 5
}
fail-fast 机制
在遍历过程中修改集合会抛出 ConcurrentModificationException。
fail-fast 示例
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
// ❌ 遍历时直接删除 → ConcurrentModificationException
for (String s : list) {
if ("b".equals(s)) list.remove(s);
}
// ✅ 用 Iterator.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("b".equals(it.next())) it.remove();
}
// ✅ Java 8+ removeIf
list.removeIf("b"::equals);
fail-fast vs fail-safe
- fail-fast:ArrayList、HashMap 的迭代器,检测到并发修改立即报错
- fail-safe:CopyOnWriteArrayList、ConcurrentHashMap 的迭代器,遍历的是快照/弱一致性视图,不报错
详见 迭代器与 fail-fast。
常见面试问题
Q1: Iterator 和 ListIterator 的区别?
答案:
| 维度 | Iterator | ListIterator |
|---|---|---|
| 方向 | 只能向前 | 可以双向(hasPrevious/previous) |
| 修改 | 只能 remove | 可以 add、set、remove |
| 索引 | 无 | 可获取当前索引 |
| 适用 | 所有 Collection | 仅 List |
Q2: for-each 循环的原理?
答案:
编译器将 for-each 转换为 Iterator 调用。对于数组则转换为普通 for 循环。所以任何实现了 Iterable 接口的类都能用 for-each。
Q3: Stream 和 Iterator 的区别?
答案:
| 维度 | Iterator | Stream |
|---|---|---|
| 风格 | 命令式(外部迭代) | 声明式(内部迭代) |
| 复用 | 迭代器用完需重新获取 | Stream 只能消费一次 |
| 并行 | 手动实现 | parallelStream() |
| 惰性求值 | 不支持 | 中间操作惰性执行 |
详见 Stream API。