Java 8 引入 Lambda 表達式(JSR 335),標誌著 Java 正式擁抱函數式編程。本文基於《Java SE 8 for the Really Impatient》讀書筆記,系統梳理 Lambda 語法規則、函數式接口體系、方法引用、構造器引用、變量捕獲語義,並對比傳統匿名內部類展示代碼簡化效果。
// 完整語法
(參數列表) -> { 方法體 }
// 核心規則:
// 1. 參數類型可省略(編譯器類型推導)
// 2. 單參數且類型可推導時,括號可省略
// 3. 單語句方法體可省略大括號與 return 關鍵字
// 4. 多語句必須用大括號包裹,且必須有返回值(非 void 類型)
| 場景 | 代碼 | 說明 |
|---|---|---|
| 無參數 | () -> System.out.println("Hello") | Runnable 風格 |
| 單參數(省略類型/括號) | s -> s.length() | Function<String, Integer> |
| 單參數(顯式類型) | (String s) -> s.length() | 類型無法推導時必須顯式 |
| 多參數 | (a, b) -> a + b | BinaryOperator<Integer> |
| 多參數(顯式類型) | (int x, int y) -> x * y | |
| 多語句體 | (x, y) -> { int z = x + y; return z * 2; } | 需大括號、return |
| 帶註解/修飾符 | (@NonNull final String s) -> s.trim() | 參數可加 final/@Annotation |
定義:有且僅有一個抽象方法的接口(SAM - Single Abstract Method)。可用 @FunctionalInterface 註解標記,編譯器會強制校驗。
| 接口 | 方法簽名 | 語義 | 常用場景 | 基本類型特化 |
|---|---|---|---|---|
Predicate<T> | boolean test(T t) | 斷言/過濾 | Stream.filter、條件判斷 | IntPredicate 等 |
Consumer<T> | void accept(T t) | 消費/副作用 | forEach、日誌記錄 | IntConsumer 等 |
Function<T, R> | R apply(T t) | 轉換/映射 | Stream.map、數據轉換 | IntFunction<R> 等 |
Supplier<T> | T get() | 供給/工廠 | 懶加載、對象創建 | BooleanSupplier 等 |
UnaryOperator<T> | T apply(T t) | 一元運算(輸入輸出同類型) | 字符串處理、數值變換 | IntUnaryOperator |
BinaryOperator<T> | T apply(T t1, T t2) | 二元運算(歸約/合併) | reduce、聚合計算 | IntBinaryOperator |
Comparator<T> | int compare(T o1, T o2) | 比較/排序 | Collections.sort、Stream.sorted | - |
Runnable | void run() | 無參無返任務 | 線程、線程池提交 | - |
Callable<V> | V call() | 有返回值任務 | Future、CompletableFuture | - |
// 傳統匿名內部類 (Java 7)
new Thread(new Runnable() {
@Override public void run() { System.out.println(Thread.currentThread().getName()); }
}).start();
// Lambda (Java 8+)
Runnable runner = () -> System.out.println(Thread.currentThread().getName());
new Thread(runner).start();
// 或直接
new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
// Comparator 排序
// 匿名內部類
Arrays.sort(strings, new Comparator() {
public int compare(String a, String b) { return Integer.compare(a.length(), b.length()); }
});
// Lambda
Arrays.sort(strings, (a, b) -> Integer.compare(a.length(), b.length()));
// 方法引用(下節詳述)
Arrays.sort(strings, Comparator.comparingInt(String::length));
當 Lambda 體僅調用一個現有方法時,可用 :: 運算符引用該方法,增強可讀性。
| 類型 | 語法 | 對應 Lambda | 示例 |
|---|---|---|---|
| 靜態方法引用 | ClassName::staticMethod | (args) -> ClassName.staticMethod(args) | Integer::parseInt |
| 實例方法引用(特定對象) | instance::method | (args) -> instance.method(args) | System.out::println |
| 實例方法引用(任意對象) | ClassName::method | (obj, args) -> obj.method(args) | String::compareToIgnoreCase |
| 構造器引用 | ClassName::new | (args) -> new ClassName(args) | ArrayList::new, String[]::new |
List list = Arrays.asList("Mary", "had", "a", "little", "lamb");
// 1. 任意對象實例方法引用:String.compareToIgnoreCase(String)
// Comparator 需要 (String, String) -> int
// String::compareToIgnoreCase 簽名為 (String) -> int,第一個參數成為調用者
list.sort(String::compareToIgnoreCase);
// 2. 靜態方法引用:自定義比較器
public class MyUtils {
public static int compareByLength(String a, String b) {
return Integer.compare(a.length(), b.length());
}
}
list.sort(MyUtils::compareByLength);
// 3. 構造器引用:Stream 轉數組
Stream
Lambda 只能捕獲 final 或 effectively final(實際上不可變) 的局部變量。
| 版本 | 匿名內部類 | Lambda 表達式 |
|---|---|---|
| Java 7 | 必須顯式 final | - |
| Java 8+ | 可省略 final,但必須 effectively final | 同左,捕獲的是值的副本,而非變量引用 |
public void repeatMessage(String text, int count) {
// count 未聲明 final,但初始化後未修改 → effectively final ✅
Runnable r = () -> {
for (int i = 0; i < count; i++) { // 捕獲 count 的值
System.out.println(text); // 捕獲 text 的值
}
};
new Thread(r).start();
}
public void repeatMessage2(String text, int count) {
// count 在 Lambda 內部修改 → 編譯錯誤 ❌
Runnable r = () -> {
while (count > 0) { // Error: Variable used in lambda expression should be final or effectively final
count--;
System.out.println(text);
}
};
}
// 變通方案:使用可變容器(單元素數組/AtomicInteger)
public void repeatMessage3(String text, int count) {
AtomicInteger counter = new AtomicInteger(count); // 線程安全可變容器
Runnable r = () -> {
while (counter.getAndDecrement() > 0) {
System.out.println(text);
}
};
new Thread(r).start();
}
// 實例/靜態字段無限制(捕獲 this 引用)
private int instanceCount;
public void useField() {
Runnable r = () -> { instanceCount++; }; // ✅ 合法,但需注意線程安全
}
this 指向外部類實例,若長生命周期持有 Lambda 會阻礙外部對象 GC。List、Map、計數器)必須使用線程安全容器或同步機制。Serializable 只有當目標類型也是 Serializable 時才可序列化(如分佈式框架 Spark/Flink 需注意)。lambda$method$0,建議提取關鍵邏輯為命名方法便於調試。java.util.function 標準接口,避免自定義單方法接口造成生態割裂。@FunctionalInterface
public interface Calculator {
T calculate(T a, T b);
// 默認方法:提供通用輔助能力,不破壞函數式接口定義
default Calculator andThen(Calculator after) {
return (a, b) -> after.calculate(this.calculate(a, b), b);
}
}
// 使用
Calculator add = (a, b) -> a + b;
Calculator mul = (a, b) -> a * b;
Calculator addThenMul = add.andThen(mul);
System.out.println(addThenMul.calculate(2, 3)); // (2+3)*3 = 15
Lambda 表達式不僅是語法糖,更是 Java 向聲明式、數據流編程邁出的關鍵一步。配合 Stream API、Optional、CompletableFuture,構成了現代 Java 並發與數據處理的核心工具箱。深入理解其類型推導、捕獲語義與方法引用機制,是寫出優雅、高效、可維護 Java 代碼的必修課。