请向我解释一个lambda表达式如何使用和修改其封闭类的实例变量,但只能使用其封闭范围的局部变量。 (除非是最终的或有效的最终决定?)
我的基本问题是,在范围的上下文中,类的实例变量如何在lambda中修改而局部变量不是可修改的。
答案 0 :(得分:8)
首先,我们可以看看JLS,其中说明了以下内容:
使用但未在lambda表达式中声明的任何局部变量,形式参数或异常参数必须声明为final或者是有效的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!
这是因为在这里返回lambda与使用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之后期望foo“为所有人”提供新值。
如果你有一个原始值,它将放在堆栈上。所以你不能简单地引用局部变量,因为它可能在你的方法结束后消失。
答案 1 :(得分:0)
您可以参考本文 - https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood解释lambda表达式编译。正如所解释的,lambda表达式/代码块被编译到匿名类中,这些匿名类使用名称格式(<<Enclosing Class name>>$<<1(Number)>>
)进行编译,因此假设如果允许非最终局部变量,则编译器无法从此处跟踪它局部变量称为匿名类&#39; &#39;的.class&#39;文件是使用上述格式创建/编译的,就像普通的java类一样。
因此,如果局部变量是最终的,那么编译器会在anoymous类中创建一个最终实例,它不会给编译器带来歧义。 有关更多信息,请参阅上面提到的链接