引言
Java 中的局部类(Local Class,本地类)和匿名类(Anonymous Class)都存在变量捕获(captured variable)。
只有理解了什么是变量捕获之后,我们后续才能更好地理解 Lambda 表达式的作用域,因为 Lambda 表达式也存在变量捕获。
什么是变量捕获
首先,我们知道局部类和匿名类都可以访问封闭类的成员,例如以下代码:
package com.wuxianjiezh.test;
public class ScopeTest {
interface Say {
public void say();
}
private String member = "我是封闭类的成员";
public void print() {
class LocalClass {
public void say() {
// 局部类可以访问封闭类的成员
System.out.println(member);
}
}
LocalClass localClass = new LocalClass();
localClass.say();
}
public void print2() {
Say say = new Say() {
@Override
public void say() {
// 匿名类可以访问封闭类的成员
System.out.println(member);
}
};
say.say();
}
public static void main(String[] args) {
ScopeTest test = new ScopeTest();
test.print();
test.print2();
}
}
此外,局部类和匿名类也都可以访问局部变量(本地变量)。但是,它们只能访问由 final
声明的局部变量。当局部类或匿名类访问封闭块的局部变量或参数时,还会捕获该局部变量或参数。譬如以下代码:
package com.wuxianjiezh.test;
public class ScopeTest {
interface Say {
public void say();
}
private String member = "我是封闭类的成员";
public void print(final String param) {
final String local = "我是局部变量";
class LocalClass {
public void say() {
// 局部类可以访问封闭类的成员
System.out.println(member);
// 局部类可以访问封闭块的最终局部变量和参数
// 从 Java 8 开始,在方法中声明的局部类可以访问方法的参数
System.out.println(param + "\t" + local);
}
}
LocalClass localClass = new LocalClass();
localClass.say();
}
public void print2(final String param) {
final String local = "我是局部变量";
Say say = new Say() {
@Override
public void say() {
// 匿名类可以访问封闭类的成员
System.out.println(member);
// 匿名类可以访问封闭块的最终局部变量和参数
// 从 Java 8 开始,在方法中声明的匿名类可以访问方法的参数
System.out.println(param + "\t" + local);
}
};
say.say();
}
public static void main(String[] args) {
ScopeTest test = new ScopeTest();
test.print("方法一");
test.print2("方法二");
}
}
上面代码中的变量 local
我们就称之为捕获的变量(captured variable)。
注意,从 Java 8 开始,局部类和匿名类都可以访问最终(final)或实际上的最终(effectively final)的封闭块的局部变量和参数。
-
实际上的最终(effectively final)
- 在初始化之后其值永远不会改变的变量或参数。
譬如,以下代码从 Java 8 开始都是有效的:
package com.wuxianjiezh.test;
public class ScopeTest {
interface Say {
public void say();
}
private String member = "我是封闭类的成员";
public void print(String param) {
// 从 Java 8 开始有效
String local = "我是局部变量";
class LocalClass {
public void say() {
// 局部类可以访问封闭类的成员
System.out.println(member);
// 局部类可以访问封闭块的实际上的最终局部变量和参数
// 从 Java 8 开始,在方法中声明的局部类可以访问方法的参数
System.out.println(param + "\t" + local);
}
}
LocalClass localClass = new LocalClass();
localClass.say();
}
public void print2(String param) {
// 从 Java 8 开始有效
String local = "我是局部变量";
Say say = new Say() {
@Override
public void say() {
// 匿名类可以访问封闭类的成员
System.out.println(member);
// 匿名类可以访问封闭块的实际上的最终局部变量和参数
// 从 Java 8 开始,在方法中声明的匿名类可以访问方法的参数
System.out.println(param + "\t" + local);
}
};
say.say();
}
public static void main(String[] args) {
ScopeTest test = new ScopeTest();
test.print("方法一");
test.print2("方法二");
}
}
但是,当我们使用赋值语句改变了实际上的最终变量时,编译器就会报错。譬如以下代码在编译时就会报错:
package com.wuxianjiezh.test;
public class ScopeTest {
interface Say {
public void say();
}
private String member = "我是封闭类的成员";
public void print(String param) {
// 从 Java 8 开始有效
String local = "我是局部变量";
class LocalClass {
public void say() {
// 局部类可以访问封闭类的成员
System.out.println(member);
// 编译器报错:Variable 'param' is accessed
// from within inner class, needs to be final or effectively final
param = "改变";
// 编译器报错:Variable 'local' is accessed
// from within inner class, needs to be final or effectively final
local = "改变";
// 局部类可以访问封闭块的实际上的最终局部变量和参数
// 从 Java 8 开始,在方法中声明的局部类可以访问方法的参数
System.out.println(param + "\t" + local);
}
}
LocalClass localClass = new LocalClass();
localClass.say();
}
public void print2(String param) {
// 从 Java 8 开始有效
String local = "我是局部变量";
Say say = new Say() {
@Override
public void say() {
// 匿名类可以访问封闭类的成员
System.out.println(member);
// 编译器报错:Variable 'param' is accessed
// from within inner class, needs to be final or effectively final
param = "改变";
// 编译器报错:Variable 'local' is accessed
// from within inner class, needs to be final or effectively final
local = "改变";
// 匿名类可以访问封闭块的实际上的最终局部变量和参数
// 从 Java 8 开始,在方法中声明的匿名类可以访问方法的参数
System.out.println(param + "\t" + local);
}
};
say.say();
}
public static void main(String[] args) {
ScopeTest test = new ScopeTest();
test.print("方法一");
test.print2("方法二");
}
}
总结
最后,我们可以总结出几下点:
- 匿名类除了没有名字外,其它和局部类并没有什么不同(这不是本文的结论,这是官方的说法:“They are like local classes except that they do not have a name.”)
- 从 Java 8 开始,局部类和匿名类都可以访问最终(final)或实际上的最终(effectively final)的封闭块的局部变量和参数
- 实际上的最终是指在初始化之后其值永远不会改变的变量或参数