Java Lambda 表达式(二):作用域
引言
正如我们之前在 和 两篇文章中所说的,局部类和匿名类都存在变量捕获和变量隐藏,而今天我们说到的 Lambda 表达式却有些许不同:Lambda 表达式存在变量捕获,但没有变量隐藏。
更进一步说,Lambda 表达式的作用域是词法作用域,这表示:
-
Lambda 表达式不会从超类型继承任何名称或引入新级别的作用域 Lambda 表达式中的声明被解释为和封闭环境中的一样
示例代码
譬如以下的代码:
package com.wuxianjiezh.test; import java.util.function.Consumer; public class ScopeTest { public int x = 0; class InnerClass { // 内部类 `InnerClass` 中的变量 `x` 隐藏了与封闭类 `ScopeTest` 中同名的变量 `x` public int x = 1; void print(int x) { // 与内部类和匿名类一样,Lambda 表达式也会有变量捕获: // 只能访问封闭块中的最终(final) // 或实际上的最终(effectively final)(从 Java 8 开始)的局部变量(本地变量)和参数。 // // 由于这个赋值语句,导致参数 `x` 不再是实际上的最终变量。 // 以下语句会导致编译器在语句 A 处报错: // Variable used in Lambda expression should be final or effectively final // // x = 99; int y = 11; // 由于这个赋值语句,导致局部变量 `y` 不再是实际上的最终变量。 // 以下语句会导致编译器在语句 B 处报错: // Variable used in Lambda expression should be final or effectively final // // y = 12; Consumer<Integer> consumer = z -> { // Lambda 表达式不会引入新级别的作用域(即不会隐藏变量),故以下语句会导致编译器报错: // Variable x is already defined in the scope // // int x = 1; // Lambda 表达式存在变量捕获,故以下语句会导致编译器报错: // Variable used in Lambda expression should be final or effectively final // x = 100; // Lambda 表达式不会引入新级别的作用域, // 故可以在 Lambda 表达式中直接访问当前封闭作用域的字段、方法(参数)和局部变量: // System.out.println("x = " + x); // 语句 A,直接访问封闭作用域中的方法参数 System.out.println("y = " + y); // 语句 B,直接访问封闭作用域中的局部变量 System.out.println("z = " + z); System.out.println("this.x = " + this.x); // 使用 `this` 引用内部类的成员变量 System.out.println("ScopeTest.this.x = " + ScopeTest.this.x);// 使用变量所属的类名来引用较大范围的成员变量 }; consumer.accept(x); } } public static void main(String... args) { ScopeTest scopeTest = new ScopeTest(); InnerClass innerClass = scopeTest.new InnerClass(); innerClass.print(21); } }
以上代码的输出结果为:
x = 21 y = 11 z = 21 this.x = 1 ScopeTest.this.x = 0
总结
为了便于记忆,我们可以将 Lambda 表达式作用域总结为以下几点:
-
Lambda 表达式是词法作用域,即它没有自己的作用域 Lambda 表达式没有自己的 this 关键字 Lambda 表达式有变量捕获,但没有变量隐藏