引言
正如我们之前在 Java 变量捕获(Captured Variable)和 Java 变量隐藏(Shadow)两篇文章中所说的,局部类和匿名类都存在变量捕获和变量隐藏,而今天我们说到的 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 表达式有变量捕获,但没有变量隐藏