1、问题产生的背景
先在方法内部初始化一个Map,然后不同的条件下通过其它服务接口返回一个Map赋给这个Map,最后在lambda表达式中使用这个Map获取值,结果报编译错误 Variable used in lambda expression should be final or effectively final。
直接上代码
2、解决
不初始化Map,如图
3、为什么?
lambda表达式只能调用局部变量,却不能改变其值。因此,只要进入方法中的lambda表达式,就会被认为是final类型。但是修改引用类型里面的属性值是没有问题,因为本质上变量的地址还是没变。
由此可见,如果直接给这个Map加上final修饰,那么调用其它服务将结果赋值给这个Map是行不通,这等于是改变了引用地址。如果不初始化,只是定义出来,那么在赋值时才初始化,后续没了重新赋值的操作,虽不符合final要求,但符合effectively final(变量没有更新,引用对象引用地址没有更新)要求。
4、为什么lambda表达式中使用的局部变量需要是final或effectively final呢?
原因可能有:
1、局部内部类**(lambda表达式等同于匿名内部类,在方法中又是局部的)**的生命周期肯定是比局部变量的生命周期要长。因为局部变量在方法结束后就没了,但是局部内部类和其他的类一样,要所有的引用都没有了才会被回收,那么如果方法结束后这个匿名内部类还没回收的话,就肯定不行的(内部类要用的局部变量随着方法结束被回收了),就会报错。那么加final就行了吗?对的,如果一个 final 变量被封装到一个匿名内部类、Lambda 表达式或方法引用中,该变量会被隐式地转换为一个成员变量,并且其生命周期将是整个匿名对象的生命周期,等于说它的生命周期被延长了
2、一个方法对应一个栈帧,lambda表达式中的逻辑也是方法,这就有两个栈帧,栈帧之间是不能访问各自的局部变量的,所以局部变量在传递到内部类时,是复制一份到自己的构造函数中。那么如果局部变量在外面被修改了,就会造成内外不同步问题,lambda为避免这个问题,就直接禁止修改,因此需要final修饰,或者知道没有改动(effectively final)。
5、为什么成员变量不用final就可以访问了?
同上面两个原因:
1、生命周期角度:成员变量是和这个类的对象挂钩的,这个匿名内部类没有被回收,这个对象肯定就不会被回收,那么这个成员变量就肯定存在。就不用担心生命周期的问题
2、数据同步角度:成员变量是存在在堆中的,堆是线程共享的,那么在lambda对应的栈中,就可以直接访问到这个成员变量,数据被修改后lambda表达式就一定能同步到,不会产生脏数据问题。
补充个更直观的测试代码: