带有 lambda 表达式的 Java 闭包
当 lambda 表达式引用封闭范围(全局或局部)的变量时,将创建 lambda 闭包。执行此操作的规则与内联方法和匿名类的规则相同。
** 来自 lambda 中使用的封闭范围的局部变量必须是 final
。使用 Java 8(支持 lambdas 的最早版本),它们不需要在外部上下文中声明 final
,但必须以这种方式处理。例如:
int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = n;
// do something
};
只要 n
变量的值没有改变,这是合法的。如果你尝试在 lambda 内部或外部更改变量,你将收到以下编译错误:
“从 lambda 表达式引用的局部变量必须是最终的或有效的最终 ”。
例如:
int n = 0;
Runnable r = () -> { // Using lambda
int i = n;
// do something
};
n++; // Will generate an error.
如果有必要在 lambda 中使用更改变量,通常的方法是声明变量的 final
副本并使用副本。例如
int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = k;
// do something
};
n++; // Now will not generate an error
r.run(); // Will run with i = 0 because k was 0 when the lambda was created
当然,lambda 的主体看不到原始变量的变化。
请注意,Java 不支持真正的闭包。无法以允许它查看实例化环境中的更改的方式创建 Java lambda。如果要实现观察或更改其环境的闭包,则应使用常规类来模拟它。例如:
// Does not compile ...
public IntUnaryOperator createAccumulator() {
int value = 0;
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
由于前面讨论过的原因,上面的例子不会编译。我们可以解决编译错误,如下所示:
// Compiles, but is incorrect ...
public class AccumulatorGenerator {
private int value = 0;
public IntUnaryOperator createAccumulator() {
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
}
问题是这破坏了 IntUnaryOperator
接口的设计合同,该接口声明实例应该是功能性的和无状态的。如果将这样的闭包传递给接受功能对象的内置函数,则可能导致崩溃或错误行为。封装可变状态的闭包应该作为常规类实现。例如。
// Correct ...
public class Accumulator {
private int value = 0;
public int accumulate(int x) {
value += x;
return value;
}
}