【ERROR】Lambda表达式无法通过编译

编译器告警内容

Variable used in lambda expression should be final or effectively final

image

使用IDEA给的解决方案

image

出现问题的原因

lambda表达式中使用的变量应该是final或者有效的final,也就是说,lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

如果一个变量(当前作用域)允许被第二次赋值,则 Lambda 表达式会抛出编译错误。

Java 8 之后,在匿名类或 Lambda 表达式中访问的局部变量,如果不是 final 类型的话,编译器自动加上 final 修饰符,即Java8的特性:effectively final。

思考

外部的局部变量 final 和匿名内部类里面的 final 是否是同一个变量?

每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接,方法出口等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

在执行方法的时候,局部变量会保存在栈中,方法结束局部变量也会出栈,随后会被垃圾回收掉,而此时,内部类对象可能还存在,如果内部类对象这时直接去访问局部变量的话就会出问题,因为外部局部变量已经被回收了,解决办法就是把匿名内部类要访问的局部变量复制一份作为内部类对象的成员变量,查阅资料或者通过反编译工具对代码进行反编译会发现,底层确实定义了一个新的变量,通过内部类构造函数将外部变量复制给内部类变量。

总结

Java lambda 表达式可以随意引用外部变量,但如果外部变量是在当前作用域声明的,则一定不可以进行第二次赋值,哪怕是在 lambda 语句之后。

image

样例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void streamMap() {
List<Integer> streamList = Arrays.asList(10, 9, 8, 7, 6, 5, 4, 3, 2, 1);

List<Integer> oneList = Arrays.asList(1, 2, 3, 4);
List<Integer> twoList = Arrays.asList(5, 6, 7, 8);
// 二次赋值
oneList = Arrays.asList(2,3);
// 编译器给的解决方案
List<Integer> finalOneList = oneList;
streamList.stream().forEach(item -> {
if (item > finalOneList.get(0)){
log.info("-----------");
}else {
log.info("===========");
}
});
finalOneList = twoList;
}

【ERROR】Lambda表达式无法通过编译
https://hif.icu/error_lambda_final/
作者
HiF
发布于
2023年9月15日
许可协议