foreach是编译器语法糖:集合遍历转为Iterator调用,数组遍历转为下标循环;遍历时修改集合会触发ConcurrentModificationException;forEach()方法与for-each循环底层不同,前者是JDK8默认方法,后者是编译期转换。

foreach 循环不是新机制,它是编译器层面的语法糖——对集合遍历时,底层自动转成 Iterator 调用;对数组遍历时,则转成传统下标 for 循环。你写的每行 for (String s : list),javac 编译后都等价于手动调用 iterator().hasNext() 和 next()。
foreach 遍历集合时到底发生了什么
当你写:
ArrayListlist = new ArrayList<>(); list.add("a"); list.add("b"); for (String s : list) { System.out.println(s); }
编译器会把它“展开”为(逻辑等价):
Iteratorit = list.iterator(); while (it.hasNext()) { String s = it.next(); System.out.println(s); }
-
list必须实现Iterable接口(ArrayList、HashSet、LinkedList都满足) - 如果集合没实现
Iterable(比如自定义类未重写iterator()),直接用foreach会编译失败 - 数组是特例:它不实现
Iterable,但foreach对数组的支持由 JVM 直接支持,底层走的是array.length+ 下标访问
为什么遍历时删元素会抛 ConcurrentModificationException
因为 foreach 依赖迭代器,而绝大多数集合的迭代器是「快速失败」(fail-fast)的。只要集合结构被修改(add/remove),但迭代器没收到通知,下一次 next() 就会检测到 modCount != expectedModCount 并立即抛异常。
立即学习“Java免费学习笔记(深入)”;
- 错误写法:
for (String s : list) { if ("b".equals(s)) list.remove(s); // ⚠️ 运行时报 ConcurrentModificationException } - 正确做法:改用
Iterator.remove():Iterator
it = list.iterator(); while (it.hasNext()) { String s = it.next(); if ("b".equals(s)) it.remove(); // ✅ 安全删除 } - 注意:
forEach()(方法引用)和stream().forEach()同样受此限制,不能在其中修改原集合
forEach() 方法和 for-each 循环不是一回事
这是最容易混淆的点:for (T t : coll) 是语句(JDK 1.5 引入),而 coll.forEach(action) 是 Iterable 接口的默认方法(JDK 8 引入),两者底层不同:
-
for-each:编译期语法糖,生成字节码级Iterator调用 -
forEach(Consumer):运行期方法调用,参数是函数式接口Consumer,本质是把 lambda 包装成accept()调用 - 性能上无本质差异,但
forEach()更适合链式操作(如配合stream().filter().forEach()) - 二者都不能在遍历中修改集合结构,也不能获取当前索引(
List也不行)
真正容易被忽略的是:数组的 foreach 和集合的 foreach 共享同一套语法,却走完全不同的底层路径——一个靠 JVM 原生支持,一个靠接口契约。写业务代码时感觉不到区别,但一旦涉及字节码分析、自定义容器或性能敏感场景,这个分界就立刻变得关键。










