java - 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
。
我很欣赏我上面提到的所有事实的完整详细答案。
解决方案
我读到这些变量是最终的,因为并发问题。
错了,这与并发无关,这完全是关于 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 做不到那。
为了使它看起来像这样,局部变量必须是有效的最终变量,因此(内部)变量根本没有被捕获这一事实对运行代码没有任何影响。
推荐阅读
- python - 如何使用python制作一个对数据框进行子集化的函数
- python - MagicMock 在 python 3.x 中获取数据的响应
- asp.net-core - NU1202 包与 netcoreapp2.1 不兼容。包支持net472
- python - 如何调用在 python 中的实际代码之后定义的函数?
- sql - 双字节空格
- jpa - JPA Criteria API 过滤子类的属性
- node.js - 在节点中使用 Sapper ESM 模块加载器:`import *` 失败
- raku - 使用特征应用代理
- flask - Flask 应用程序上下文:app.app_context().push() 有效,但无法让“with app.app_context()”块工作
- ruby-on-rails - 理解捆绑器依赖解析