Lambda 表达式和变量捕获
请向我解释 lambda 表达式如何使用和修改其封闭类的实例变量,但只能使用其封闭作用域的局部变量。(除非是最终或有效的最终决定?
我的基本问题是,在作用域的上下文中,类的实例变量如何从 lambda 中可修改,而局部变量则不能。
请向我解释 lambda 表达式如何使用和修改其封闭类的实例变量,但只能使用其封闭作用域的局部变量。(除非是最终或有效的最终决定?
我的基本问题是,在作用域的上下文中,类的实例变量如何从 lambda 中可修改,而局部变量则不能。
首先,我们可以看一下JLS,它声明如下:
在 lambda 表达式中使用但未声明的任何局部变量、形式参数或异常参数都必须声明为 final 或实际上是 final (§4.12.4),否则在尝试使用时会发生编译时错误。
在 lambda 主体中使用但未声明的任何局部变量都必须在 lambda 主体之前明确赋值 (§16(定赋值)),否则会发生编译时错误。
关于变量使用的类似规则也适用于内部类的主体 (§8.1.3)。对有效最终变量的限制禁止访问动态变化的局部变量,这些局部变量的捕获可能会引入并发问题。与最终的限制相比,它减轻了程序员的文书负担。
对有效最终变量的限制包括标准循环变量,但不包括增强型循环变量,对于循环的每次迭代,这些变量被视为不同 (§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!
这是因为在此处返回 lambda 与使用 创建一个新的匿名类大致相同。您的 lambda - 的实例具有对创建它的类的引用。你可以重写 to use ,这将执行完全相同的操作。这就是允许您访问(和修改)实例变量的原因。new Consumer<String>() {...}
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后会期望“给所有人”新值。foo
如果你有一个基元值,它将位于堆栈上。因此,您不能简单地引用局部变量,因为它可能会在到达方法的末尾后消失。
您可以参考本文 - https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood lambda 表达式编译中介绍的内容。如前所述,lambda表达式/代码块被编译到匿名类中,这些匿名类是用名称格式()编译的,所以假设如果允许非最终局部变量,那么编译器就无法跟踪它,这个局部变量被称为匿名类''.class'文件是像普通的java类一样分别使用上述格式创建/编译的。<<Enclosing Class name>>$<<1(Number)>>
因此,如果局部变量是最终的,那么编译器会在 anoymous 类中创建一个最终实例,这不会给编译器带来歧义。有关详细信息,请参阅上面提到的链接