Java 8 Lambda 表達式完整指南:函數式介面、方法引用、預設方法、Stream API 實戰
Lambda 表達式是 Java 8 最大亮點,將函數式程式設計引入 Java,讓代碼更簡潔、更具表達力。本文系統梳理函數式介面、Lambda 語法、方法引用、介面預設方法、集合框架增強與 Stream API 核心操作。
一、函數式介面 — Lambda 的載體
- 定義:有且僅有一個抽象方法的介面 (可含預設/靜態方法)
- 註解:
@FunctionalInterface 編譯期驗證,防止誤添加抽象方法
- 經典例子:
Runnable、Comparator<T>、Callable<V>、Predicate<T>、Function<T,R>、Consumer<T>、Supplier<T>
@FunctionalInterface
public interface MyFunc {
void execute(String param); // 唯一抽象方法
default void log() { System.out.println("預設方法"); } // 允許預設方法
}
二、Lambda 語法
(參數列表) -> 表達式
(參數列表) -> { 語句塊; return 結果; }
| 形式 | 範例 | 說明 |
| 無參無返 | () -> System.out.println("Hi") | Runnable |
| 單參無返 | (s) -> System.out.println(s) | 括號可省略:s -> ... |
| 多參有返 | (a, b) -> a + b | 隱式返回,無需 return |
| 複雜邏輯 | (x, y) -> { int z = x+y; return z*2; } | 需大括號與 return |
三、方法引用 — Lambda 的語法糖
格式:類名::方法名 或 實例::方法名 或 類名::new
| 類型 | 語法 | 對應 Lambda |
| 靜態方法 | Class::staticMethod | (args) -> Class.staticMethod(args) |
| 實例方法 | obj::instanceMethod | (args) -> obj.instanceMethod(args) |
| 任意對象實例方法 | Class::instanceMethod | (obj, args) -> obj.instanceMethod(args) |
| 建構子 | Class::new | (args) -> new Class(args) |
// 範例:啟動執行緒
Runnable r1 = () -> System.out.println("Lambda");
Runnable r2 = Demo::staticRun; // 靜態方法引用
Runnable r3 = new Demo()::instanceRun; // 實例方法引用
new Thread(r2).start();
四、介面預設方法 — 介面演進的關鍵
default 關鍵字定義實現方法,實現類可選擇覆寫
- 解決「介面添加方法破壞現有實現」的二進位相容性問題
- 集合框架大量使用:
Iterable.forEach(), Collection.stream(), Map.computeIfAbsent() 等
- 多介面衝突:實現類必須顯式覆寫並選擇調用
InterfaceA.super.method()
五、介面 vs 抽象類 (Java 8 後)
| 維度 | 介面 | 抽象類 |
| 多重實現/繼承 | 支援多實現 | 單繼承 |
| 方法實現 | default/static 方法 | 任意方法 |
| 構造子 | 無 | 有 |
| 狀態 (欄位) | 僅 static final 常數 | 任意實例欄位 |
| 設計意義 | 行為契約 (like-a) | 實體層次 (is-a) |
六、集合框架增強 — 內部迭代
List users = Arrays.asList(
new User(20, "張三"), new User(22, "李四"), new User(10, "王五"));
// forEach + Lambda
users.forEach(u -> System.out.println(u.getName() + ":" + u.getAge()));
// 移除條件元素 (Java 8 新增)
users.removeIf(u -> u.getAge() < 18);
// 替換所有元素
users.replaceAll(u -> new User(u.getAge() + 1, u.getName()));
七、Stream API — 宣告式資料處理
特點:無儲存、不修改源、延遲執行 (終端操作觸發)、可並行 (parallelStream())、一次性消費。
核心操作分類
| 類型 | 操作 | 回傳 | 說明 |
| 中間操作 | filter(Predicate) | Stream | 過濾 |
map(Function) | Stream | 映射轉換 |
sorted(Comparator) | Stream | 排序 |
distinct() | Stream | 去重 |
| 終端操作 | forEach(Consumer) | void | 遍歷消費 |
collect(Collector) | R | 收集 (toList, toSet, groupingBy...) |
count() | long | 計數 |
reduce() | Optional | 歸約聚合 |
anyMatch/allMatch/noneMatch | boolean | 匹配判斷 |
實戰範例
List users = ...;
// 1. 篩選年齡 ≥ 20,取名字轉大寫,收集為 List
List names = users.stream()
.filter(u -> u.getAge() >= 20)
.map(u -> u.getName().toUpperCase())
.collect(Collectors.toList());
// 2. 統計年齡 ≥ 20 的人數
long count = users.stream()
.filter(u -> u.getAge() >= 20)
.count();
// 3. 找出最年長者
Optional oldest = users.stream()
.max(Comparator.comparing(User::getAge));
// 4. 分組:按年齡段分組
Map> groups = users.stream()
.collect(Collectors.groupingBy(u -> {
if (u.getAge() < 18) return "青少年";
else if (u.getAge() < 60) return "成年";
else return "長者";
}));
// 5. 並行流處理大數據 (多核 CPU 加速)
List bigData = ...;
bigData.parallelStream()
.filter(s -> s.length() > 10)
.collect(Collectors.toList());
八、最佳實踐建議
- 優先使用方法引用而非簡單 Lambda,可讀性更高
- 避免在 Lambda 中修改外部變數 (需 final/effectively final)
- Stream 管道保持純淨,副作用操作放在
forEach 終端
- 大數據量才用
parallelStream(),小數據開銷大於收益
- 善用
Optional 避免 NPE,而非直接 get()