foreach是语法糖,编译后转为迭代器或数组索引访问;遍历Iterable调用iterator()等方法,遍历数组则用索引循环;自定义类需实现Iterable接口;ArrayList的foreach会抛ConcurrentModificationException,除非用Iterator.remove()。

foreach 是语法糖,编译后变成迭代器或数组索引访问
Java 中的 for (Type item : collection) 不是独立语法结构,而是编译器在编译期展开的语法糖。它没有运行时开销,但行为取决于被遍历对象的类型:若为 Iterable(如 ArrayList、HashSet),则转为调用 iterator() + hasNext() + next();若为数组,则转为传统 for 索引循环。
- 集合类必须实现
Iterable接口,否则编译报错:Can only iterate over an array or an instance of java.lang.Iterable - 数组不走迭代器,所以不会触发
ConcurrentModificationException,但也不能在遍历时修改数组长度(本质不可变) - 自定义类想支持 foreach,只需实现
Iterable并返回有效Iterator,无需注解或额外声明
为什么 ArrayList 的 foreach 不会抛 ConcurrentModificationException?
它会抛——只要在遍历过程中通过非迭代器方式修改了结构(如调用 list.add() 或 list.remove())。这是因为 ArrayList.iterator() 返回的是 Itr 内部类,其 checkForComodification() 方法会在每次 next() 前比对 modCount 和 expectedModCount。
- 使用
Iterator.remove()是安全的,它会同步更新expectedModCount - 使用
removeIf()或removeAll()也会重置迭代器状态,但它们本身是批量操作,不是在 foreach 循环体内调用 - 增强 for 循环无法获取底层
Iterator引用,所以无法在循环中安全删除元素
foreach 遍历 Map.entrySet() 时拿到的是 Map.Entry 对象
写 for (Map.Entry 是最常见且推荐的方式。注意 entrySet() 返回的是视图(view),不是副本,所以修改 entry.setValue() 会直接反映到原 Map 中。
-
keySet()和values()也能用 foreach,但前者只能读 key,后者连 key 都拿不到,且values()视图不支持setValue() - 不要写
for (String key : map.keySet()) { map.get(key); }—— 这多了一次哈希查找,不如直接遍历entrySet() -
HashMap的entrySet()迭代顺序不保证;如需有序,请用LinkedHashMap或TreeMap
性能和可读性权衡:什么时候不该用 foreach?
当需要索引、需要并行处理、或需要在循环中移除多个元素时,foreach 就成了障碍。它的简洁是以牺牲控制力为代价的。
立即学习“Java免费学习笔记(深入)”;
- 要索引?改用传统
for (int i = 0; i ,避免每次调用list.get(i)前还去查size()(虽然 HotSpot 会优化,但语义更清晰) - 要并发遍历?
forEach()(Iterable没这方法)、parallelStream().forEach()才是正确路径,普通 foreach 是单线程的 - 要过滤并收集?直接上
stream().filter().collect(),别在 foreach 里手动new ArrayList()+add()
真正容易被忽略的是:foreach 的变量作用域仅限于循环体,且每次迭代都绑定新引用——但这不意味着对象被复制,只是引用重新赋值。如果循环体内缓存了某个 entry 或 item 到外部集合,要注意它指向的仍是原对象,后续修改会影响所有持有者。









