Lambda 中的变量捕获

Variable capture in Lambda

我不明白为什么捕获的变量在 lambda 表达式中是最终的或实际上是最终的。我查看了 this question 并没有得到答案。

这个变量捕获是什么?

当我搜索我的问题的解决方案时,我读到这些变量是最终的,因为并发问题。但是对于这种情况,为什么我们不能用 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 因为它是一个局部变量。(员工只是一个 class getters 和 setters of int i.)

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

我感谢对我上面提到的所有事实的完整详细回答。

I read that these variables are final because of concurrency problems.

错了,这与并发无关,它是关于lambdas(和匿名classes)如何"capture"变量值。

I want know the reasons why the last three statements gives an error

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

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

i like to know why we can mutate this.instance

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


原因

lambda 主要是匿名 class 的语法糖。这不是真的,但为了这个解释的目的,它已经足够了,并且对于匿名 class.

的解释更容易理解

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

但是,由于 Java(语言)有匿名的 classes,而 JVM 没有,编译器必须伪造它,将匿名的 class 转换为内部 class。 (仅供参考:内部 classes 也不存在于 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);

对于匿名class,编译器会生成这样的class:

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);

这就是 捕获 的工作方式,通过 复制 "captured" 变量的值。

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

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

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

为了代码清晰,as-if i 局部变量 captured,并且[匿名 class 中的 =17=] 与外部的 i 相同 ,因为那是 Java 语言定义的内容,尽管 JVM 无法做到这一点。

为了让它出现那样,局部变量必须是有效的final,所以(内部)变量根本没有 captured 这一事实对 运行 代码没有影响。