整理 Effective Java 書中 Item 42: Prefer lambda expressions to anonymous classes 心得筆記

主旨

在 Java 8 之前,若要實作一個函式物件(function object),通常會使用匿名類別。但寫法冗長、閱讀不易。Java 8 引入 Lambda 表達式後,可以用更簡潔的語法來表示這些功能。這篇文章會說明為什麼你應該「偏好使用 Lambda」,並補充 Lambda 和匿名類別的異同、注意事項,以及何時該避免使用 Lambda。

點出問題:匿名類別太冗長

匿名類別是 Java 中實作單一抽象方法介面的傳統方式,但它的語法很繁瑣。例如,我們想根據字串長度排序清單:

Collections.sort(words, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return Integer.compare(s1.length(), s2.length());
    }
});

整個 new Comparator<>() 的寫法、方法宣告等,都會佔掉不少篇幅。如果我們只是想排序,這段程式碼的意圖反而被冗長的語法蓋掉了。

劃重點:Lambda 更精簡、更清楚

Lambda 的寫法就簡潔多了:

Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));

這邊可以省略類型,因為編譯器會自動推斷。Lambda 主要是為了解決像這種「單一抽象方法介面」的問題,這類介面稱作 Functional Interface。Java 內建了很多這類介面,例如:

  • Comparator<T>
  • Runnable
  • Callable<V>
  • Function<T, R>
  • Predicate<T>

這些都可以透過 Lambda 直接實作,讓你少打很多樣板程式碼。

範例:簡化 Enum 行為實作

在 Item 34 中提到的 Operation Enum,如果要讓每個常數有自己的邏輯,以前只能靠覆寫抽象方法:

public enum Operation {
    PLUS("+") {
        public double apply(double x, double y) { return x + y; }
    },
    ...
}

現在,我們可以用 Lambda 儲存行為實作:

public enum Operation {
    PLUS("+", (x, y) -> x + y),
    MINUS("-", (x, y) -> x - y),
    TIMES("*", (x, y) -> x * y),
    DIVIDE("/", (x, y) -> x / y);

    private final String symbol;
    private final DoubleBinaryOperator op;

    Operation(String symbol, DoubleBinaryOperator op) {
        this.symbol = symbol;
        this.op = op;
    }

    public double apply(double x, double y) {
        return op.applyAsDouble(x, y);
    }
}

這樣不只簡潔,還讓 enum 每個常數的行為更清晰易讀。

真實世界範例:字串過濾器

假設我們有一個商品名稱清單,想篩選出名字含有「Pro」的項目:

List<String> products = Arrays.asList("MacBook Pro", "iPad", "AirPods Pro");
List<String> result = products.stream()
    .filter(name -> name.contains("Pro"))
    .collect(Collectors.toList());

Lambda 在這種資料處理上非常方便,搭配 Stream API 就可以像在寫 SQL 一樣,讓邏輯更貼近需求。

劃重點:Lambda 的限制

雖然 Lambda 很方便,但也有以下限制:

  • 不能處理非 Functional Interface(例如有多個抽象方法的介面)
  • 沒有名稱與註解,不適合放太複雜的邏輯(建議一行為限,最多三行)
  • this 指的是外層類別,而非 Lambda 自己(匿名類別則不同)
  • 無法自我參照(無法在 Lambda 裡呼叫自身)
  • 無法被序列化成跨平台傳遞的格式

若你遇到以上限制,或需要較多邏輯控制,建議還是用匿名類別或命名類別。

小結

從 Java 8 開始,使用 Lambda 是實作簡單功能邏輯的首選。它讓 Java 程式更簡潔、可讀性更高。不過要記得:

  • 若邏輯超過三行、需要註解或錯誤處理,請改用方法或命名類別
  • 匿名類別在一些特定情境(例如需要 self-reference 或實作多方法介面)仍有其用武之地
  • Lambda 的出現,讓 Java 更接近函數式程式設計,但也需要善用其優勢與避免其陷阱