Variable 'xxxx' is accessed from within inner class, needs to be final or effectively final-Lambda 表达式的变量与作用域

发布时间 2023-07-03 23:49:08作者: 旅途的痕迹

问题的原因

问题代码:

 public static void main(String[] args) {
      Integer sum = 0;
      Integer count = 0;
      List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5));
      list.stream().forEach(e->{
           sum+=e; //这步会编译错误--Variable used in lambda expression should be final or effectively final
      });
 }

Variable 'xxxx' is accessed from within inner class, needs to be final or effectively final-Lambda 这段话翻译成中文为:变量“xxxx”是从内部类中访问的,需要是final或既成事实上的 final变量。意思就是sum需要为一个final对象或者说既成事实上的 final变量(所谓个既成事实上的 final 变量是指只能给变量赋值一次),如果sum满足以上要求代表sum不能被重新赋值了。

解决方法:

public static void main(String[] args) {
     JSONObject param = new JSONObject();
     param.put("sum",0);
     param.put("count",0);
     List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5));
     list.stream().forEach(e->{
         param.put("sum",param.getInteger("sum")+e);
         param.put("sum",param.getInteger("count")+1);
     });
}

在外面套一层对象,这里的param就是既成事实上的 final变量,因为在这段代码中param就被赋值过一次。

拓展-访问对象字段与静态变量

Lambda 内部对于实例的字段和静态变量是即可读又可写的。

public class Question2 {
     int sum = 0;
     static int count = 0;
     public static void main(String[] args) {
          Question2 question2 = new Question2();
          List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5));
          list.stream().forEach(e->{
               question2.sum+=e;
               Question2.count++;
          });
     }
}

小结

Lambda 表达式可以读写实例变量,只能读取局部变量。

思考

  • Lambda 表达式访问非 final 的局部变量,这是为什么呢?
    首先思考外部的局部变量 final 和匿名内部类里面的 final 是否是同一个变量?
    我们知道,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接,方法出口等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程(《深入理解Java虚拟机》第2.2.2节 Java虚拟机栈)。就是说在执行方法的时候,局部变量会保存在栈中,方法结束局部变量也会出栈,随后会被垃圾回收掉,而此时,内部类对象可能还存在,如果内部类对象这时直接去访问局部变量的话就会出问题,因为外部局部变量已经被回收了,解决办法就是把匿名内部类要访问的局部变量复制一份作为内部类对象的成员变量,查阅资料或者通过反编译工具对代码进行反编译会发现,底层确实定义了一个新的变量,通过内部类构造函数将外部变量复制给内部类变量。
  • 为何还需要用 final 修饰?
    其实复制变量的方式会造成一个数据不一致的问题,在执行方法的时候局部变量的值改变了却无法通知匿名内部类的变量,随着程序的运行,就会导致程序运行的结果与预期不同,于是使用final修饰这个变量,使它成为一个常量,这样就保证了数据的一致性。

参考

https://no8gs.blog.csdn.net/article/details/117333667