Variable used in lambda expression should be final or effectively final

4,015 阅读3分钟

问题描述:

使用Java8表达式的时候编辑,会时不时遇到这样的编译报错,如下图:

image.png

Variable used in lambda expression should be final or effectively final这句话的意思是,lambda表达式中使用的变量应该是final或者有效的final,为什么会有这种规定呢?其实在 Java 8 之前,匿名类中如果要访问局部变量的话,那个局部变量必须显式的声明为 final。Java 8 之后,在匿名类或 Lambda 表达式中访问的局部变量,如果不是 final 类型的话,编译器自动加上 final 修饰符。

如上图,String是引用数据类型,值为null,因此并没有在堆内存开辟空间,只是栈内存当中存储了一个变量名为str而已,而这个str没有存储任何地址值,并且如果Lambda表达式内部如果要是使用该变量的话,编译器就会默认给它用final进行修饰,因此在Lambda内部对这个str赋值 x + "111" 时,则需要在堆内存开辟一块空间,并将地址值赋值给str,而str是用final进行修饰的,因此编译就会报错。

final拓展:

1.修饰类:当用final修饰一个类时,表明这个类不能被继承(修饰类时类中的所有的方法会隐式的指定final方法)。

2.修饰方法:禁止该方法在子类中被重写!

3.修饰变量:如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

解决办法:

既然引用地址值无法改变,那么我们只需要改变该地址值所对应堆内存存储的数据即可。

@Test
public void contextLoad1s() {
    //案例一:
public void contextLoad1s() {
    String[] strArray = {"a", "b", "c"};
    StringBuilder str = new StringBuilder();
    Arrays.asList(strArray).forEach(x ->
            str.append(String.join("_", x, "111"))
    );
    System.out.println(str);//a_111b_111c_111

    //案例二:
    List<CoinWallet> coinWallets = new ArrayList<>();
    voterSet.forEach(user -> {
        coinWallets.addAll(nftWikiUserApi.getCoinWallets(user));
    });
}

为什么lambda 表达式或者匿名内部类不能访问非 final 的局部变量?

在lambda表达式中对变量的操作都是基于原变量的副本,不会影响到原变量的值。假定没有要求lambda表达式外部变量为final修饰,那么开发者会误以为外部变量的值能够在lambda表达式中被改变,而这实际是不可能的,所以要求外部变量为final是在编译期以强制手段确保用户不会在lambda表达式中做修改原变量值的操作。

其实这就要说到Jvm内存模型和线程了,因为实例变量存在堆中,而局部变量是在栈上分配,lambda 表达(匿名内部类) 会在另一个线程中执行。如果在线程中要直接访问一个局部变量,可能线程执行时该局部变量已经被销毁了,而 final 类型的局部变量在 Lambda 表达式(匿名类) 中其实是局部变量的一个拷贝。