请向我解释lambda表达式如何使用和修改它的封装类的实例变量,并且只能使用它的封闭范围的局部变量(除非它是final或有效的final)? 我的基本问题是类的实例变量中的λ是如何修改的,并且局部变量不是在范围Lambda表达式和变量捕获
回答
方面首先,我们可以看看在JLS,其中规定了以下内容:
使用但未在lambda表达式中声明的任何局部变量,形式参数或异常参数都必须声明为final或有效最终(§4.12.4),否则会发生编译时错误,其中尝试使用。
任何使用但未在lambda体中声明的局部变量必须在lambda体之前明确赋值(§16(Definite Assignment)),否则会发生编译时错误。
有关变量使用的相似规则适用于内部类的主体(第8.1.3节)。对有效最终变量的限制禁止访问动态更改的局部变量,它们的捕获可能会引入并发问题。与最终的限制相比,它减少了程序员的文书负担。
对有效最终变量的限制包括标准循环变量,但不包括增强型for循环变量,循环变量在循环的每次迭代中都被视为不同(第14.14.2节)。
为了更好地理解它,看看这个例子类:
输出的public class LambdaTest {
public static void main(String[] args) {
LambdaTest test = new LambdaTest();
test.returnConsumer().accept("Hello");
test.returnConsumerWithInstanceVariable().accept("Hello");
test.returnConsumerWithLocalFinalVariable().accept("Hello");
}
String string = " world!";
Consumer<String> returnConsumer() {
return ((s) -> {System.out.println(s);});
}
Consumer<String> returnConsumerWithInstanceVariable() {
return ((s) -> {System.out.println(s + string);});
}
Consumer<String> returnConsumerWithLocalFinalVariable() {
final String foo = " you there!";
return ((s) -> {System.out.println(s + foo);});
}
}
主要是
Hello
Hello world!
Hello you there!
这是因为这里返回拉姆达是多少与创建new Consumer<String>() {...}
的新匿名类相同。你的lambda - 一个Consumer<String>
的实例有一个对它创建的类的引用。你可以重写returnConsumerWithInstanceVariable来使用System.out.println(s + LambdaTest.this.string)
,这将完全相同。这就是允许你访问(和修改)实例变量的原因。
如果你的方法中有一个(有效的)最终局部变量,你可以访问它,因为你可以访问它,因为它被复制到你的lambda实例。
但是,但是,如果它不是终点,你觉得在以下方面应该发生:
Consumer<String> returnConsumerBad() {
String foo = " you there!";
Consumer<String> results = ((s) -> {System.out.println(s + foo);});
foo = " to all of you!";
return results;
}
如果该值被复制到您的实例,但后来没有更新,当局部变量更新?这可能会引起混淆,因为我认为许多程序员会希望富美在返回此lambda后拥有“给所有人”的新价值。
如果你有一个原始值,它会放在堆栈上。所以你不能简单地引用局部变量,因为它可能会在方法结束后消失。
你可以参考这篇文章 - https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
关于lambda表达式编译的解释。正如所解释的lambda表达式/代码块被编译到匿名类中,这些匿名类将使用名称格式(<<Enclosing Class name>>$<<1(Number)>>
)进行编译,因此假设假设允许非最终局部变量,那么编译器无法从引用该局部变量的位置追踪它因为匿名类的.class文件是用前述格式创建/编译的,就像普通的java类一样。
因此,如果局部变量是最终的,那么编译器会在不会给编译器造成歧义的类动态类中创建一个最终实例。 请参考上面的链接提供更多信息
- 1. Lambda表达式,捕获的变量和线程
- 2. 捕获和在C++ 14 lambda表达式
- 3. Lambda变量捕获
- 4. 如何避免在lambda表达式中捕获变量?
- 5. 如何调整Vala中的Lambda表达式捕获的变量?
- 6. lambda表达式和全局变量
- 7. 终身拉姆达的常量lambda表达式捕获引用
- 8. Lambda表达式偷偷`捕获``
- 9. Lambda Metafactory变量捕获
- 10. Lambda表达式/委托赋值变量
- 11. C++中的lambda表达式变量
- 12. 修改lambda表达式中的变量
- 13. 在lambda表达式中指定捕获的变量的目的是什么?
- 14. 解决方案:没有可以捕获受管变量的lambda表达式
- 15. Perl 6正则表达式变量和捕获组
- 16. C++ - lambda表达式,捕获子句和类成员
- 17. 变换lambda表达式
- 18. C++ lambda - 捕获成员变量
- 19. lambda函数混淆捕获的变量
- 20. 强制C++ 11 lambda捕获变量
- 21. 正则表达式量化捕获
- 22. 量化捕获的正则表达式
- 23. C++ lambda表达式和静态变量的预期行为
- 24. Lambda表达式,并发和静态变量
- 25. 从lambda捕获捕获变量会出错?
- 26. lambda表达式和的putIfAbsent
- 27. Lambda表达式和搜索
- 28. Lambda表达式和RVO
- 29. 谓词和lambda表达式
- 30. C#Lambda表达式和NHibernate
由于lambda实际上是一个匿名内部类,这是[为什么只有最终变量可以在匿名类中访问?](http://stackoverflow.com/questions/ 4732544/why-only-final-variables-accessible-in-anonymous-class) – mthmulders
对于Java 8来说这是真实的,但据我所知它没有被指定,所以这可以在未来版本的Java中改变。 – Alex
只有'this'被捕获。所有对lambda体内实例变量的访问都是通过'this.field'完成的,因此你也可以写入它。 – ZhongYu