Lambda 表达式线上踩坑案例¶
结论¶
Lambda 本身不是问题,但它与 effectively final 变量捕获、延迟执行、序列化、this 指向结合时,容易在线上引发隐蔽 bug。
坑 1:捕获可变变量导致编译错误¶
Lambda 只能捕获 effectively final 的局部变量:
int sum = 0;
list.forEach(x -> sum += x); // 编译错误:Variable used in lambda should be final or effectively final
错误写法:用数组或 AtomicInteger 绕过去:
正确做法:用 Stream:
坑 2:循环变量捕获错误¶
Java 8 之前会编译错误(i 不是 final);Java 8 中 i 在循环里每次重新赋值,Lambda 捕获的是同一个变量,实际输出可能全是 10。
修复:循环内复制:
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> System.out.println(finalI)).start();
}
坑 3:this 的指向¶
匿名内部类的 this 指向匿名类自己;Lambda 的 this 指向外层类:
public class Demo {
public void run() {
Runnable r = () -> System.out.println(this.getClass());
r.run(); // Demo,不是 Lambda 也不是 Synthetic
}
}
线上如果有人把匿名内部类改写成 Lambda,行为可能变化(特别是依赖 this 的场景)。
坑 4:序列化问题¶
Lambda 默认不支持可靠序列化。即使能序列化,反序列化需要编译器生成的 writeReplace,跨 JDK 版本会失败。不要把 Lambda 写到 Redis / 磁盘。
坑 5:异常检查与受检异常¶
Lambda 内部不能直接抛受检异常,除非函数式接口声明了 throws:
坑 6:Stream 并行陷阱¶
list.parallelStream().forEach(System.out::println); // 顺序不保证
list.parallelStream().reduce(...) // 必须用无状态、结合律的 accumulator
并行流用公共 ForkJoinPool.commonPool(),IO 密集型任务会拖垮整个池子。
坑 7:日志与 Lambda¶
logger.debug("value: " + expensive()); // 不管日志级别,expensive() 都会执行
logger.debug("value: {}", () -> expensive()); // 只有 debug 开启才执行(SLF4J lambda 版)
线上案例总结
- 用 Lambda 替代匿名内部类时,重点检查
this、变量捕获、异常。 - 不要在 Lambda 里修改外部状态,保持纯函数。
- 并行流谨慎用于 IO 场景。