Lambda 表達式是 Java 8 最具革命性的特性,它將函數式編程引入 Java,配合 Stream API 實現集合的聲明式、並行化處理。本文系統講解 Lambda 語法規則、函數式接口體系、實戰應用場景、變量捕獲規則、方法引用、默認方法及生成器函數等核心知識點。
Lambda 本質是匿名方法,由三部分組成:參數列表 -> 箭頭 -> 方法體(表達式或代碼塊)。
// 標準形式:帶類型參數、表達式體
(int x, int y) -> x + y
// 類型推導:省略參數類型
(x, y) -> x + y
// 代碼塊體:需顯式 return
(x, y) -> { return x + y; }
// 無參數
() -> System.out.println("Hello Lambda!")
// 單參數且類型可推導:省略括號
s -> s.length()
// 多行語句塊
(str) -> {
System.out.println("Processing: " + str);
return str.toUpperCase();
}
Lambda 並非 Object,其類型稱為目標類型,必須是函數式接口——有且僅有一個抽象方法的接口(可標註 @FunctionalInterface)。
Runnable、自定義 MyRunnable),只要方法簽名兼容。Object:Object o = (Runnable) () -> {};。Comparator 雖聲明兩方法(compare、equals),但 equals 來自 Object,不計入抽象方法計數,故仍是函數式接口。| 接口 | 抽象方法 | 語義 | 典型場景 |
|---|---|---|---|
Function<T,R> | R apply(T t) | 輸入 T,輸出 R | 數據轉換、映射 |
Consumer<T> | void accept(T t) | 消費 T,無返回 | 遍歷打印、持久化 |
Predicate<T> | boolean test(T t) | 判斷 T 是否滿足條件 | 過濾、斷言 |
Supplier<T> | T get() | 無輸入,產出 T | 延遲初始化、工廠 |
UnaryOperator<T> | T apply(T t) | 一元運算,輸入輸出同類型 | 數值計算、字符串處理 |
BinaryOperator<T> | T apply(T t1, T t2) | 二元運算 | 聚合、減少 |
另有雙參數 BiFunction、BiConsumer、BiPredicate 及基本類型特化版(IntFunction 等)避免裝箱開銷。
// 舊寫法
new Thread(new Runnable() {
public void run() { System.out.println("old"); }
}).start();
// Lambda
new Thread(() -> System.out.println("lambda")).start();
編譯器根據上下文(Thread 構造器參數為 Runnable)自動推導目標類型。
Stream 實現內部迭代,配合 Lambda 完成 Filter-Map-Reduce 流水線操作:
List<String> numbers = Arrays.asList("1", "2", "3", "4", "5", "6");
// 篩選偶數 -> 轉整數 -> 求和
int sum = numbers.stream()
.filter(s -> Integer.parseInt(s) % 2 == 0)
.map(Integer::parseInt)
.reduce(0, Integer::sum);
System.out.println(sum); // 12
filter、map、distinct、sorted。collect、forEach、reduce、count、toArray。limit、findFirst、anyMatch。// 利用多核並行處理
list.parallelStream().filter(...).map(...).collect(...);
// 或
list.stream().parallel()...
並行流自動分割任務到 ForkJoinPool,適用於 CPU 密集、數據量大、無副作用的純函數操作。
// 分組統計
Map<Integer, Long> freq = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.groupingBy(n -> n, Collectors.counting()));
// 分區
Map<Boolean, List<Integer>> part = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// 嵌套 Lambda
Callable<Runnable> c = () -> () -> System.out.println("nested");
// 條件表達式
Callable<Integer> c2 = flag ? () -> 42 : () -> 24;
// 遞歸 Lambda(需聲明為字段或使用 UnaryOperator)
UnaryOperator<Integer> fact = n -> n == 0 ? 1 : n * fact.apply(n - 1);
fact.apply(5); // 120
注意:Java 不支持即時調用 ((x,y)->x+y)(1,2),Lambda 僅可用於賦值、傳參、返回值。
Lambda 可訪問外部作用域變量(成員變量、靜態變量、局部變量),但局部變量必須是 final 或 effectively final(僅賦值一次,不再修改)。
int tmp1 = 1; // 實例變量 OK
static int tmp2 = 2; // 靜態變量 OK
final int tmp3 = 3; // 顯式 final OK
int tmp4 = 4; // effectively final OK
int tmp5 = 5;
Function<Integer,Integer> f = i -> {
// tmp5 += i; // 編譯錯誤:修改導致非 effectively final
return tmp5;
};
// tmp5 = 9; // 編譯錯誤:後續修改同樣導致非 effectively final
原因:Lambda 可能在不同線程執行,捕獲可變局部變量會導致線程安全問題。實例/靜態變量存儲在堆中,天然可見,無此限制。
當 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::length |
| 構造器引用 | ClassName::new | (args) -> new ClassName(args) | ArrayList::new |
| 數組構造器引用 | Type[]::new | len -> new Type[len] | String[]::new |
// 原始
list.stream().map(s -> new Integer(s)).filter(Primes::isPrime).distinct().sum();
// 方法引用全程
list.stream().map(Integer::new).filter(Primes::isPrime).distinct().sum();
Java 8 允許接口聲明 default 方法(提供默認實現)與 static 方法,解決集合接口添加 stream()、forEach() 等方法時的二進制兼容性問題。
public interface MyInterface {
void abstractMethod();
default void defaultMethod() {
System.out.println("Default impl");
}
static void staticMethod() {
System.out.println("Static in interface");
}
}
class Sub implements Base1, Base2 {
public void hello() {
Base1.super.hello(); // 顯式選擇
}
}
Stream.generate(Supplier<T>) 由供給函數產生無限流,需配合 limit() 等短路操作終止:
// 生成 5 個隨機數
Stream.generate(Math::random).limit(5).forEach(System.out::println);
// 生成常量流
Stream.generate(() -> "constant").limit(3).forEach(System.out::print);
Lambda 表達式不僅是語法糖,更是 Java 向函數式編程邁出的關鍵一步。掌握其語法、類型系統、Stream 配合模式及底層捕獲語義,是現代 Java 開發者的核心技能。