首页 > 解决方案 > Lambda 中的变量捕获

问题描述

我想不出为什么捕获的变量在 lambda 表达式中是最终的或有效的最终变量。我看了看这个问题,真的没有得到答案。

这个变量捕获是什么?

当我为我的问题搜索解决方案时,我读到这些变量是最终的,因为并发问题。但是对于这种情况,为什么我们不能用reentrant lock对象锁定 lambda 中的任务代码。

public class Lambda {

  private int instance=0;

  public void m(int i,String s,Integer integer,Employee employee) {

    ActionListener actionListener = (event) -> {
      System.out.println(i);
      System.out.println(s);
      System.out.println(integer);
      System.out.println(employee.getI());
      this.instance++;
      employee.setI(4);
      integer++;//error
      s="fghj";//error
      i++;//error
    };
  }

}

在这个特定的代码中,我想知道最后三个语句给出错误的原因,以及为什么我们要变异Employee,因为它是一个局部变量。(Employee 只是一个具有 getter 和 setter 的类int i。)

我也想知道为什么我们也可以变异this.instance

我很欣赏我上面提到的所有事实的完整详细答案。

标签: javalambdafinal

解决方案


我读到这些变量是最终的,因为并发问题。

错了,这与并发无关,这完全是关于 lambdas(和匿名类)如何“捕获”变量值。

我想知道最后三个语句出错的原因

因为它们是捕获的,所以它们必须是有效的最终

您真的不需要知道为什么内部需要这样做,只需接受您需要遵守该规则的事实即可。

我想知道为什么我们可以变异this.instance

因为代码不捕获 instance,它捕获 this,并且this隐含地是最终的。


原因

lambda 主要是匿名类的语法糖。这不是真的,但是为了这个解释的目的,它已经足够真实了,并且对于匿名类来说,这个解释更容易理解。

首先要明白,JVM中没有匿名类这种东西。实际上,也没有 lambda 表达式这样的东西,但那是另一回事。

但是,由于 Java(该语言)具有匿名类,而 JVM 没有,因此编译器必须通过将匿名类转换为内部类来伪造它。(仅供参考:JVM 中也不存在内部类,因此编译器也必须伪造它。)

让我们举个例子。假设我们有这个代码:

// As anonymous class
int i = 0;
Runnable run = new Runnable() {
    @Override
    public void run() {
        System.out.println(i);
    }
}

// As lambda expression:
int i = 0;
Runnable run = () -> System.out.println(i);

对于匿名类,编译器会生成这样一个类:

final class Anon_1 implements Runnable {
    private final int i;
    Anon_1(int i) {
        this.i = i;
    }
    @Override
    public void run() {
        System.out.println(i);
    }
}

然后将代码编译为:

int i = 0;
Runnable run = new Anon_1(i);

这就是捕获的工作原理,通过复制“捕获”变量的值。

变量根本没有被捕获,值是,因为 Java 在构造函数调用中是按值传递的。

现在你可以争辩说,没有理由i应该是有效的最终结果。当然,局部变量i和字段i现在是分开的,但它们可以单独修改。

但这是有原因的,而且这是一个非常好的理由。被复制的事实i是独立的,是完全隐藏的,是一个实现细节。程序员会不断地忘记这一点,并认为它们是相同的,这将导致大量代码失败,并且需要提醒许多浪费时间的调试时间。

为了代码清晰,它必须是好像i本地变量被捕获了,并且i匿名类中的与外部相同i,因为这是 Java 语言定义的,即使 JVM 做不到那。

为了使它看起来像这样,局部变量必须是有效的最终变量,因此(内部)变量根本没有被捕获这一事实对运行代码没有任何影响。


推荐阅读