首先,我们可以看一下JLS,它声明如下:
在 lambda 表达式中使用但未声明的任何局部变量、形式参数或异常参数必须声明为 final 或有效地为 final(第 4.12.4 节),否则在尝试使用时会发生编译时错误。
在 lambda 主体中使用但未声明的任何局部变量必须在 lambda 主体之前明确分配(第 16 节(定义分配)),否则会发生编译时错误。
变量使用的类似规则适用于内部类的主体(第 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);});
}
}
main 的输出是
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;
}
是否应该将值复制到您的实例,但在更新局部变量时不会更新?这可能会引起混淆,因为我认为许多程序员会期望 foo 在返回此 lambda 后具有“对所有人”的新值。
如果你有一个原始值,它会放在堆栈上。所以你不能简单地引用局部变量,因为它可能会在你的方法结束后消失。