增强型for循环是编译器语法糖:遍历数组时编译期展开为索引循环,不使用iterator;遍历iterable集合时则真实依赖iterator接口实现。

增强型 for 循环(for-each)本质是编译器语法糖,它**自动使用迭代器遍历数组或实现了 Iterable 接口的集合**。对数组而言,它并非真正调用 Iterator,而是由编译器在编译期展开为传统索引循环;但对集合(如 ArrayList、HashSet),它确实依赖 Iterator 接口实现——这点常被误解,需分清场景。
for-each 遍历数组:编译期展开,不涉及 Iterator
Java 中,for (int x : arr) 这类写法作用于数组时,底层不会生成 Iterator 对象,也不会调用 hasNext() 或 next()。Javac 编译器会直接将其转为等效的传统 for 循环:
// 源码
int[] arr = {1, 2, 3};
for (int x : arr) {
System.out.println(x);
}
// 编译后等效代码(反编译可见)
for (int i = 0; i
这种转换高效、无额外对象开销,也解释了为什么数组的 for-each 不能获取当前索引(除非手动维护变量),也不支持删除元素(没有迭代器状态可操作)。
for-each 遍历集合:真实调用 Iterator
当用于 ArrayList、LinkedList 等集合时,for-each 才真正委托给集合的 iterator() 方法:
- 编译器插入
Iterator<t> it = collection.iterator()</t> - 每次循环检查
it.hasNext(),并执行T item = it.next() - 若在遍历中通过集合自身方法(如
list.remove())修改结构,会触发ConcurrentModificationException
例如:
List<string> list = new ArrayList(Arrays.asList("a", "b", "c"));
for (String s : list) { // 实际调用 list.iterator()
if ("b".equals(s)) list.remove(s); // ❌ 抛出 ConcurrentModificationException
}
</string>
如何验证底层行为?看字节码或反编译
可通过 javap -c 查看编译后的字节码,或用 JD-GUI 等工具反编译 class 文件:
- 数组遍历:字节码中出现
arraylength、iaload、iinc(索引递增)等指令,无invokeinterface Iterator.next - 集合遍历:明确包含
invokeinterface Collection.iterator和连续的invokeinterface Iterator.hasNext/next
这是理解“语法糖”本质最直接的方式,避免仅凭表象猜测运行机制。
何时该用 for-each?注意这些限制
for-each 简洁安全,但有明确适用边界:
- ✅ 适合:只读遍历、无需索引、不修改集合结构
- ❌ 不适合:需要下标(如同时处理
i和arr[i+1])、遍历中删除元素(应改用显式 Iterator.remove())、需并行多路遍历(如双指针) - ⚠️ 特别注意:数组无法用 for-each 删除元素(语法不支持),集合中删除必须用
it.remove(),而非collection.remove()
理解其展开逻辑与适用条件,才能在简洁性和可控性之间做出合理选择。










