lambda expression should be final or effectively final
lambda 表达式的使用,这样使用时,代码并没有报错:
public static List<String> getMenuListByRoleId(List<Integer> roleIdList) {
List<List<String>> menuList = new ArrayList<>();
List<String> list = new ArrayList<>();
roleIdList.forEach(x-> list.addAll(menuList.get(x)));
// list = list.stream().distinct().collect(Collectors.toList());
return list;
}
但是当把注释放开,就会报错:
public static List<String> getMenuListByRoleId(List<Integer> roleIdList) {
List<List<String>> menuList = new ArrayList<>();
List<String> list = new ArrayList<>();
roleIdList.forEach(x-> list.addAll(menuList.get(x)));
list = list.stream().distinct().collect(Collectors.toList());
return list;
}

经查阅资料知:
lambda 表达式的执行原理是会构建一个内部类,其中表达式中用到的外部变量,都会通过内部类的构造函数,作为参数引入。
如上述代码中,list,menuList,roleIdList都是内部类的构造函数入参。
内部类中也存在list,menuList,roleIdList
反编译结果如下:

lambda$getMenuListByRoleId$0方法的详细执行过程如下:

aload的意思:从局部变量表的相应位置装载一个对象引用到操作数栈的栈顶
故lambda$getMenuListByRoleId$0方法只是保存了参数对象的引用,并没有在类中对引用类型做深层次的拷贝。也就是说,多线程的情况下,这个对象可以被多个线程同时引用。
考虑一种场景,多线程的情况下,lambda$getMenuListByRoleId$0类在遍历list时,外部突然把list指向了别的集合,怎么办?
所以lambda表达式规定:
Java 7 要求 所用到的局部变量必须是 final 类型的,否则在匿名类中不可引用。
Java 8 之后,你没加,编译器自动帮你加,即Java8新特性:effectively final
回顾之前的代码,便可知报错原因:
报错原因可以等价为:

即使没有使用lambda表达式,也会报错,因为在list转化的过程中,list对象的引用变了,与final的定义相悖,故会报错。
至于原先的报错,为什么idea会把提示放到第三行,lambda表示中的list,而不是第四行的list,猜测是由于使用了lambda表达式,编译器尝试给第一,二行的list添加final过程中,发现与第四行的代码相悖,无法添加,归根结底,源头还是lambda,所以才在第三行报错吧。
怎么解决?还是老老实实写for循环吧!
浙公网安备 33010602011771号