2.3 引用值,而不是变量
如果你曾使用过匿名内部类,也许遇到过这样的情况:需要引用它所在方法里的变量。这时,需要将变量声明为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。
1Lambda表达式中引用的局部变量必须是final或既成事实上的final变量。——译者注
例2-7 未使用既成事实上的final变量,导致无法通过编译
String name = getUserName();name = formatUserName(name);button.addActionListener(event -> System.out.println("hi " + name));
这种行为也解释了为什么Lambda表达式也被称为闭包。未赋值的变量与周边环境隔离起来,进而被绑定到一个特定的值。在众说纷纭的计算机编程语言圈子里,Java是否拥有真正的闭包一直备受争议,因为在Java中只能引用既成事实上的final变量。名字虽异,功能相同,就好比把菠萝叫作凤梨,其实都是同一种水果。为了避免无意义的争论,全书将使用“Lambda表达式”一词。无论名字如何,如前文所述,Lambda表达式都是静态类型的。因此,接下来就分析一下Lambda表达式本身的类型:函数接口。
