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循环吧!

 

posted @ 2021-05-29 15:56  q彩虹海q  阅读(839)  评论(0)    收藏  举报