foreach是iterable默认方法,本质为iterator()+while循环,每次新建iterator,异常中断遍历,不支持并发修改,性能不优于传统for循环,重写需谨慎。

forEach 方法在 Iterable 接口里到底做了什么
它只是个默认方法,不强制子类重写,但底层调用的是 iterator() + while 循环,没有魔法,也不做并发保护。
- 每次调用都会新建一个
Iterator实例,意味着不能复用已有的迭代器状态 - 如果传入的
Consumer抛异常,遍历立即中断,不会跳过继续执行 - 对
ArrayList这类支持随机访问的集合,forEach并不比传统for (int i = 0; ...)快——JVM 未必能内联优化这个默认方法调用 - 源码本质就三行:
Iterator<t> iterator = this.iterator(); while (iterator.hasNext()) { action.accept(iterator.next()); }</t>
为什么 List.forEach 和 for-each 循环行为看起来一样却不能互换
表面都“遍历”,但触发机制和可干预点完全不同:一个是接口契约定义的统一入口,一个是语法糖编译后生成的 iterator() 调用。
-
for (String s : list)是编译期转换,最终字节码里仍是显式hasNext()/next()调用;而list.forEach(...)是运行时动态分派,默认方法逻辑固定 - 若子类重写了
forEach(比如CopyOnWriteArrayList就重写了),list.forEach会走重写版本,但for-each仍走iterator()链路,行为可能不一致 - 调试时注意:断点打在
forEach上,进不去具体集合实现类?那说明它没重写,默认走Iterable的那个基础实现
forEach 里修改集合结构会抛 ConcurrentModificationException 吗
会,而且和你是否用 remove() 无关——只要在遍历中途改变了集合的 modCount,下一次 next() 就炸。
- 哪怕你只调
list.add(...)或list.clear(),只要发生在forEach的action里,就会触发校验失败 - 这不是
forEach特有的限制,是所有基于fail-fastIterator的遍历方式共有的约束 - 想边遍历边删?别在
forEach里硬刚,改用Iterator.remove(),或者收集待删元素后批量处理 -
CopyOnWriteArrayList.forEach是个例外:它不检查modCount,因为迭代器基于快照,但代价是看不到遍历期间的新元素
自定义集合实现 Iterable 时要不要重写 forEach
绝大多数情况不用。除非你有明确性能优势或语义差异,否则直接继承默认实现更安全。
立即学习“Java免费学习笔记(深入)”;
- 如果你的迭代逻辑特殊(比如按某种索引顺序、跳过某些元素、需要预热缓存),重写能让调用方无感受益
- 但如果只是封装了另一个集合,又没做额外控制,重写反而容易漏掉
modCount校验或异常传播逻辑 - 注意:重写
forEach不影响for-each语法,后者永远依赖iterator()返回值 - 测试时别只测“能跑”,要覆盖空集合、单元素、并发修改等边界——默认实现的健壮性已经过 JDK 长期验证,自己写的未必
真正容易被忽略的是:默认 forEach 不提供任何执行上下文控制,比如中断、跳过、并行化。它就是个最朴素的串行消费器。想做更复杂的事,别在它身上堆逻辑,该换 Stream 就换,该手写循环就写。









