Contents

Java Lambdas 筆記

Java 8 引入 Lambda 之後,很多集合操作、條件過濾、回呼寫法都變得簡潔不少。不過剛開始看 Lambda 時,如果沒有把 Functional Interface 這個前提一起理解,常常會有一種「為什麼它可以直接塞進去」的困惑。

這篇把 Lambda 最容易卡住的幾個點整理一下,用比較工程實務的角度來看會比較好理解。

Lambda 為什麼能成立

Lambda 不是憑空冒出來的新語法糖,它背後有一個重要前提:目標型別必須是 Functional Interface。

所謂 Functional Interface,就是只定義一個抽象方法的介面。例如:

1
2
3
4
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

因為這個介面只有一個抽象方法,所以編譯器看到 Lambda 時,才能知道它到底要對應到哪個方法。

常見範例

像下面這種寫法:

1
Predicate<Integer> isEven = n -> n % 2 == 0;

其實就是在建立 Predicate 的實作,等價概念可以想成是把 test() 這個方法補上去,只是寫法更短。

為什麼 removeIf() 可以直接吃 Lambda

ArrayList.removeIf() 來看:

1
list.removeIf(n -> n < 0);

這裡不是 Lambda 自己知道要呼叫什麼,而是 removeIf() 的方法簽名已經規定它要收 Predicate。因此編譯器會把 n -> n < 0 視為 Predicate.test() 的實作。

簡單講,決定 Lambda 角色的是呼叫端的型別,不是 Lambda 本身。

Lambda 好用在哪裡

在下面幾種場景特別常見:

  1. 集合過濾,例如 filter()removeIf()
  2. 排序比較,例如 Comparator
  3. 簡單回呼與策略切換。
  4. Stream API 串接。

如果這些地方還是都寫匿名類別,程式碼通常會長很多。

例外處理要注意

Lambda 內如果拋出受檢查例外,常常會遇到編譯錯誤。原因不是 Lambda 特別難搞,而是你對應的 Functional Interface 方法簽名沒有宣告該例外。

實務上常見做法有兩種:

  1. 在 Lambda 裡面 catch 之後轉成 RuntimeException。
  2. 自訂一個可以宣告 throws 的 Functional Interface。

如果你常常在 Lambda 裡處理 IO、轉檔或網路操作,這一點會很有感。

小結

Java Lambda 最重要的理解點不是語法本身,而是它依附在 Functional Interface 之上。只要先搞懂「目標型別只有一個抽象方法」,再回頭看 PredicateFunctionConsumer 這些介面,整體就會清楚很多。之後在讀 Stream API 或集合操作時,也比較不會只覺得它是魔法。