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