帶有 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;
}
}