foreach调用前提为集合非null且实现iterable接口;数组需转流或列表;map需通过entryset等调用;lambda参数类型由集合元素自动推导;不支持break/continue;并发容器遍历无顺序保证;需处理lambda内异常。

forEach 方法在 JDK8 中的调用前提
必须确保集合对象不是 null,且底层实现了 Iterable 接口(如 ArrayList、HashSet、LinkedList 等)。Arrays.asList() 返回的列表可以,但数组本身不能直接调用 forEach——它没有该方法。
常见错误现象:NullPointerException(集合为 null)、java.lang.Error: Unresolved compilation problem(误对数组调用)。
- 检查集合是否初始化:用
Objects.nonNull(list)预判,或用 Optional 包裹 - 数组要先转成流或集合:
Arrays.stream(arr).forEach(...)或Arrays.asList(arr).forEach(...) - 注意
Map不能直接forEach,需调用entrySet()、keySet()或values()
Lambda 表达式参数类型推导规则
JDK8 的 forEach 接收一个 Consumer<t></t> 函数式接口,编译器会根据集合元素类型自动推导 Lambda 参数类型,无需显式声明。
例如 List<string></string> 调用 forEach 时,e -> System.out.println(e) 中的 e 就是 String 类型;若写成 (String e) -> ... 也能通过,但冗余。
- 如果集合是原始类型包装类(如
Integer),Lambda 参数不能写成int——泛型不支持基本类型 - 多语句 Lambda 必须加花括号和
return(仅当需要返回值时),但Consumer的accept方法返回void,所以不要return表达式 - 若需访问外部变量,该变量必须是“事实上 final”(即定义后未再赋值)
与传统 for-each 循环的关键行为差异
forEach + Lambda 是内部迭代,无法使用 break 或 continue 控制流程,也不能通过索引随机访问元素。它本质是“只读遍历”,设计上不鼓励副作用操作(如修改集合本身)。
常见误用场景:想在遍历时删元素 → 触发 ConcurrentModificationException;想提前退出 → Lambda 里抛异常或用标签都不是好办法。
- 需要中断遍历?改用传统
for (String s : list)或带索引的for (int i = 0; i - 需要边遍历边删除?用
Iterator.remove(),或先收集待删项再批量移除 - 需要按索引处理?用
IntStream.range(0, list.size()).forEach(i -> {...}),但性能略低,且易出界
性能与并发容器的兼容性注意点
普通 ArrayList 或 HashMap 的 forEach 是顺序执行,无额外开销;但若换成并发容器(如 ConcurrentHashMap),其 forEach 行为不保证顺序,且可能跳过刚插入的元素(弱一致性)。
错误假设:“用了 ConcurrentHashMap.forEach 就线程安全了”——其实只是避免了 CME,不代表遍历过程看到的是某一时刻的完整快照。
- 对实时性要求高的遍历,优先用
new ConcurrentHashMap(map).keySet().toArray()再遍历副本 - 不要在 Lambda 里调用阻塞操作(如 IO、锁等待),会拖慢整个遍历线程
- 避免在 Lambda 中修改当前集合结构(add/remove),即使容器是线程安全的,也可能导致逻辑错乱
最常被忽略的一点:Lambda 体内的异常必须处理。编译器不会强制捕获受检异常,但运行时抛出 RuntimeException 会导致遍历静默终止——后续元素不再处理,且无提示。










