foreach是语法糖,底层调用Iterator;编译期重写为iterator.hasNext()和next()调用,数组则转为索引for循环,遍历时修改集合会触发ConcurrentModificationException。

foreach 是语法糖,底层调用 Iterator
Java 中的 for-each 循环不是新语句,而是编译器层面的语法糖。它在编译期被重写为显式的 Iterator 调用,等价于手动写 iterator.hasNext() 和 iterator.next()。这意味着:只要类实现了 Iterable 接口(即提供 iterator() 方法),就能用 for-each 遍历。
- 数组也支持
for-each,但走的是另一套编译路径——编译器生成基于索引的普通for循环,不经过Iterator -
Collection子类(如ArrayList、HashSet)都继承自AbstractCollection,默认复用其iterator()实现,返回各自内部的迭代器(如ArrayList$Itr) - 如果遍历时修改集合(如在循环中调用
list.remove()),会触发ConcurrentModificationException,因为迭代器有modCount检查机制
不能用 foreach 的典型场景
以下情况无法直接使用 for-each,必须退回到传统 for 或 Iterator:
- 需要在遍历中删除元素:必须用
iterator.remove(),而非集合自身的remove() - 需要访问当前索引:比如要同时处理
list.get(i)和i,for-each不暴露索引 - 遍历多个集合并行操作(如 zip):语法不支持多变量绑定
- 集合类型未实现
Iterable:比如自定义类没重写iterator(),或用了原始数组以外的非标准容器
反编译验证 foreach 的真实行为
写一段简单代码:
for (String s : list) { System.out.println(s); }然后用 javap -c 反编译,能看到类似这样的字节码片段:
12: invokevirtual #4 // Method java/util/List.iterator:()Ljava/util/Iterator; 15: astore_2 16: aload_2 17: invokeinterface #5, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 22: ifeq 41 25: aload_2 26: invokeinterface #6, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
这说明:编译器确实插入了 iterator()、hasNext()、next() 三步调用,没有魔法,只有确定的替换规则。
立即学习“Java免费学习笔记(深入)”;
增强 for 的性能与兼容性注意点
虽然写法简洁,但要注意两点隐含成本:
- 每次循环都会调用
iterator()—— 对大多数集合是轻量的(返回内部对象),但若自己实现Iterable时每次 new 一个新Iterator,就有额外开销 - 泛型擦除后,
for-each中的变量类型是运行时不可知的,强制转型失败会抛ClassCastException,且异常栈不指向循环体,调试时容易误判位置 - Android 低版本(API forEach()(方法引用式)支持不全,但传统
for-each语句始终可用,因为它是编译期特性
最常被忽略的是:迭代器的 fail-fast 行为不是线程安全保证,只是检测到结构性修改就报错;真要并发修改,得用 CopyOnWriteArrayList 或显式加锁。









