1. 認識
簡單說:Lambda 表達式是一種在需要時快速建立函式介面實作的方式,不必宣告額外的類別或匿名類別。它就像沒有名字的小型方法,可以當作參數傳遞或存到變數中。
Java 曾長期是一種相當「傳統」的 OOP 語言。若你想傳遞一段行為,例如按下按鈕時要做什麼,就得寫匿名類別:
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick() {
System.out.println("按鈕已按下!");
}
});
自 Java 8 起出現了 Lambda 魔法:
button.setOnClickListener(() -> System.out.println("按鈕已按下!"));
其中 () -> System.out.println("按鈕已按下!") 就是 Lambda 表達式。
形式化地說:Lambda 表達式是對函式介面唯一抽象方法實作的精簡寫法。
為什麼重要?
- 能把行為當成值來傳遞(例如:對清單中的每個元素要做什麼)。
- 撰寫更精簡、可讀性更高的程式碼。
- 使用現代 Java API:Stream API、事件處理、非同步任務等。
2. Lambda 表達式語法
一般型式
(parametry) -> vyrazhenie
// 或者
(parametry) -> { blok koda }
不同介面的範例
1. 無參數(例如 Runnable):
Runnable r = () -> System.out.println("哈囉,Lambda!");
r.run();
2. 一個參數(例如 Consumer):
Consumer<String> printer = s -> System.out.println("你傳入了:" + s);
printer.accept("Java");
如果只有一個參數,且其型別可被推斷,參數的小括號可以省略。
String 是變數(s)的型別,我們把它傳進去。
3. 多個參數(例如 Comparator):
Comparator<Integer> cmp = (a, b) -> a - b;
System.out.println(cmp.compare(10, 5)); // 5
Integer 是變數(a、b)的型別。
4. 多行主體(需要大括號,且若有明確回傳值需寫 return):
Function<Integer, Integer> square = x -> {
int result = x * x;
return result;
};
System.out.println(square.apply(6)); // 36
第一個 Integer 是回傳型別,第二個 Integer 是參數型別。
省略與精簡
- 若主體只有一個運算式,可省略大括號與 return。
- 若無參數,寫空的小括號:() -> ...
- 若只有一個參數,可省略小括號:x -> ...
- 若超過一個參數,需要小括號:(a, b) -> ...
總結表
看看同一個想法用 Lambda 與匿名類別如何表示:
| 介面 | Lambda 表達式(範例) | 匿名類別的等價寫法 |
|---|---|---|
|
|
|
|
|
|
|
|
|
自定義介面範例
假設我們有一個函式介面:
@FunctionalInterface
interface Operation {
int apply(int a, int b);
}
以前會這樣實作:
Operation sum = new Operation() {
@Override
public void apply(int a, int b) {
return a + b;
}
};
現在——更現代:
Operation sum = (a, b) -> a + b;
System.out.println(sum.apply(3, 5)); // 8
標準介面範例
Runnable:
Runnable hello = () -> System.out.println("Hello from thread!");
new Thread(hello).start();
Comparator:
List<String> list = Arrays.asList("蘋果", "香蕉", "奇異果");
list.sort((a, b) -> a.length() - b.length());
System.out.println(list);
Function:
Function<String, Integer> parse = s -> Integer.parseInt(s);
System.out.println(parse.apply("123")); // 123
3. 作用域與變數捕捉
外部脈絡中的變數(effectively final)
Lambda 表達式可以使用周圍方法中的變數。但有條件:那些變數必須是effectively final——也就是要麼明確宣告為 final,要麼在初始化後不再被修改。
範例:
String prefix = "結果:";
Function<Integer, String> f = x -> prefix + (x * 2);
// 這裡的 prefix 被「凍結」——之後就不能再修改
System.out.println(f.apply(5)); // 結果:10
如果嘗試在它被 Lambda 使用之後再修改 prefix,編譯器會報錯。
為什麼?
Lambda 表達式可能在宣告該變數的方法結束後才被呼叫。為避免各種「魔幻」的錯誤,Java 只允許使用不可變的變數。
與匿名類別的差異
在匿名類別中也有相同規則:外部方法的變數必須是 final/effectively final。但在作用域上有細節差異:在匿名類別內,this 參照的是匿名類別本身;而在 Lambda 中,this 參照的是外部類別。
public class Demo {
public void test() {
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println(this); // 輸出:Demo$1(匿名類別)
}
};
Runnable r2 = () -> System.out.println(this); // 輸出:Demo(外部類別)
r1.run();
r2.run();
}
}
Lambda 與類別欄位
Lambda 表達式可以不受限制地存取外部類別的欄位:
public class Counter {
private int base = 10;
public void printSum(int x) {
Function<Integer, Integer> sum = y -> base + y + x;
System.out.println(sum.apply(5));
}
}
這裡的 base——可以修改(它是類別欄位)。
x——必須是 effectively final。
4. 實作:寫幾個 Lambda 表達式
範例:篩選數字清單
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);
nums.stream()
.filter(n -> n % 2 == 0)
.forEach(n -> System.out.println("偶數:" + n));
更多關於 Stream API 的內容你會在第 30 級學到 :P
範例:字串轉換函式
Function<String, String> capitalize = s -> s.toUpperCase();
System.out.println(capitalize.apply("java")); // JAVA
範例:自訂函式介面
@FunctionalInterface
interface StringTransformer {
String transform(String s);
}
StringTransformer exclaim = s -> s + "!";
System.out.println(exclaim.transform("你好")); // 你好!
範例:使用外部脈絡中的變數
int factor = 2;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> System.out.println(n * factor));
// 之後不能修改 factor!
5. 使用 Lambda 表達式的常見錯誤
錯誤 №1:試圖修改被 Lambda 捕捉的變數。
這樣的程式不會通過編譯——該變數必須是 effectively final:
int sum = 0;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> sum += n); // 錯誤:sum 不是 final!
如果需要累加值——請使用陣列或包裝物件。
錯誤 №2:混淆 this 的作用域。
在 Lambda 表達式內,this 指向外部類別,而不是指向 Lambda(與匿名類別不同)。
錯誤 №3:在多行 Lambda 主體中忘了大括號與 return。
如果 Lambda 主體不止一個運算式,就需要大括號與 return:
Function<Integer, Integer> square = x -> {
int y = x * x;
return y;
};
錯誤 №4:錯誤地指定 Lambda 表達式的型別。
Lambda 表達式一定是實作某個函式介面。不能只寫:
var f = x -> x + 1; // 錯誤!無法得知介面型別。
必須明確指定型別:
Function<Integer, Integer> f = x -> x + 1;
GO TO FULL VERSION