为什么使用新特性java8的Lambda 表达式,如果引用方法里的变量则需要给它设为final,否则就会报错呢?(local variables referenced from a Lambda expression must be final or effectively final1)

发布时间 2023-04-13 18:48:51作者: 东方昭月
1、这是我学会使用Lambda 表达式经常困惑的问题,我在Java 8 Lambdas,Richard Warburton 著(O’Reilly,2014)中找到了原因。
2、如果你曾使用过匿名内部类,也许遇到过这样的情况:需要引用它所在方法里的变量。这
时,需要将变量声明为 final,如例 2-5 所示。将变量声明为 final,意味着不能为其重复赋
值。同时也意味着在使用 final 变量时,实际上是在使用赋给该变量的一个特定的值。
例 2-5 匿名内部类中使用 final 局部变量
final String name = getUserName();
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("hi " + name);
}
});
Java 8 虽然放松了这一限制,可以引用非 final 变量,但是该变量在既成事实上必须是
final。虽然无需将变量声明为 final,但在 Lambda 表达式中,也无法用作非终态变量。如
果坚持用作非终态变量,编译器就会报错。
既成事实上的 final 是指只能给该变量赋值一次。换句话说,Lambda 表达式引用的是值,
而不是变量。在例 2-6 中,name 就是一个既成事实上的 final 变量。
例 2-6 Lambda 表达式中引用既成事实上的 final 变量
String name = getUserName();
button.addActionListener(event -> System.out.println("hi " + name));
final 就像代码中的线路噪声,省去之后代码更易读。当然,有些情况下,显式地使用 final
代码更易懂。是否使用这种既成事实上的 final 变量,完全取决于个人喜好。
如果你试图给该变量多次赋值,然后在 Lambda 表达式中引用它,编译器就会报错。比
如,例 2-7 无法通过编译,并显示出错信息:local variables referenced from a Lambda
expression must be final or effectively final1 。
例 2-7 未使用既成事实上的 final 变量,导致无法通过编译
String name = getUserName();
name = formatUserName(name);
button.addActionListener(event -> System.out.println("hi " + name));
这种行为也解释了为什么 Lambda 表达式也被称为闭包。未赋值的变量与周边环境隔离起
来,进而被绑定到一个特定的值。在众说纷纭的计算机编程语言圈子里,Java 是否拥有真
正的闭包一直备受争议,因为在 Java 中只能引用既成事实上的 final 变量。名字虽异,功
能相同,就好比把菠萝叫作凤梨,其实都是同一种水果。
无论名字如何,如前文所述,Lambda 表达式都是静态类型的。
注 1:Lambda 表达式中引用的局部变量必须是 final 或既成事实上的 final 变量。