1. Lambda 表達式與集合
回顧在 Lambda 表達式出現之前,我們是如何處理集合的。假設有一個字串清單,想把它們輸出到螢幕上:
List<String> list = Arrays.asList("貓", "狗", "刺蝟");
for (String s : list) {
System.out.println(s);
}
這很簡單,但如果我們想刪除清單中的所有空字串,就得寫帶條件的迴圈,有時甚至必須使用 Iterator(否則會拋出 ConcurrentModificationException)。再比如,依字串長度排序:
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
就這樣的簡單任務——已經需要 5 行程式碼,加上一堆「噪音」的大括號。如何最佳化?答案你已經知道了:Lambda 表達式。
在集合方法中使用 Lambda 表達式
自 Java 8 起,集合的介面新增了接受函式式介面的各種方法——也就是說,我們可以把 Lambda 表達式傳入。以下是最常用的:
- forEach(Consumer<T> action)
- removeIf(Predicate<T> filter)
- sort(Comparator<T> c)
- replaceAll(UnaryOperator<T> operator)
範例:forEach
將列表所有元素輸出到螢幕(舊方法):
for (String s : list) {
System.out.println(s);
}
現在——用 Lambda:
list.forEach(s -> System.out.println(s));
或者更短,如果你想玩「Java 大師」的把戲:
list.forEach(System.out::println); // method reference,之後再說明
範例:removeIf
刪除清單中的所有空字串:
List<String> animals = new ArrayList<>(Arrays.asList("貓", "", "狗", "刺蝟", ""));
animals.removeIf(s -> s.isEmpty());
System.out.println(animals); // [貓, 狗, 刺蝟]
範例:sort
依字串長度排序:
List<String> animals = Arrays.asList("貓", "狗", "刺蝟", "大象");
animals.sort((a, b) -> a.length() - b.length());
System.out.println(animals); // [貓, 狗, 刺蝟, 大象]
範例:replaceAll
將所有字串轉成大寫:
List<String> animals = new ArrayList<>(Arrays.asList("cat", "dog", "hedgehog"));
animals.replaceAll(s -> s.toUpperCase());
System.out.println(animals); // [CAT, DOG, HEDGEHOG]
2. Stream API 與 Lambda 表達式
自 Java 8 起,出現了強大的 Stream API,用以支援以函式風格操作集合。透過串流可以用方法鏈來過濾、轉換、排序並收集集合。而這些方法都能接受 Lambda 表達式!
重要:之後我們會完整講解 Stream API,現在只看基礎範例,理解 Lambda 的角色。
範例:過濾
只保留長度大於 3 的字串:
List<String> animals = Arrays.asList("cat", "elephant", "eel", "crocodile");
animals.stream()
.filter(s -> s.length() > 3)
.forEach(System.out::println); // elephant, crocodile
範例:轉換(map)
把所有字串變成大寫:
List<String> animals = Arrays.asList("cat", "elephant", "eel");
List<String> upper = animals.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
System.out.println(upper); // [CAT, ELEPHANT, EEL]
範例:排序
取得依長度排序後的清單(不改變原清單):
List<String> animals = Arrays.asList("cat", "elephant", "eel", "crocodile");
List<String> sorted = animals.stream()
.sorted((a, b) -> a.length() - b.length())
.collect(Collectors.toList());
System.out.println(sorted); // [eel, cat, elephant, crocodile]
3. 與匿名類別的比較
我們來比較同一段程式碼用匿名類別與用 Lambda 的寫法。
依字串長度排序
匿名類別:
animals.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
Lambda 表達式:
animals.sort((a, b) -> a.length() - b.length());
結論:
Lambda 表達式節省大量程式碼,讓程式更易讀。括號更少、噪音更少——重點更明確!
刪除空字串
匿名類別:
animals.removeIf(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.isEmpty();
}
});
Lambda 表達式:
animals.removeIf(s -> s.isEmpty());
4. 實作練習:用 Lambda 操作使用者清單的短題目
我們用一個迷你程式來實作練習,在使用者清單上應用 Lambda 表達式。
範例 1:依年齡篩選使用者
class User {
String name;
int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
List<User> users = Arrays.asList(
new User("Alice", 17),
new User("Bob", 25),
new User("Charlie", 15)
);
users.stream()
.filter(u -> u.age >= 18)
.forEach(System.out::println); // Bob (25)
範例 2:依姓名排序使用者
List<User> users = Arrays.asList(
new User("Alice", 17),
new User("Bob", 25),
new User("Charlie", 15)
);
users.sort((u1, u2) -> u1.name.compareTo(u2.name));
System.out.println(users);
// [Alice (17), Bob (25), Charlie (15)]
範例 3:把使用者清單轉成姓名清單
List<String> names = users.stream()
.map(u -> u.name)
.collect(Collectors.toList());
System.out.println(names); // [Alice, Bob, Charlie]
範例 4:刪除所有未成年
List<User> users = new ArrayList<>(Arrays.asList(
new User("Alice", 17),
new User("Bob", 25),
new User("Charlie", 15)
));
users.removeIf(u -> u.age < 18);
System.out.println(users); // [Bob (25)]
5. 實用細節
重點:作用域與「實質上為 final」變數
Lambda 表達式可以使用外層方法的變數,但前提是它們是 final 或「實質上為 final」(也就是初始化後不再被修改)。
int minAge = 18;
users.stream()
.filter(u -> u.age >= minAge)
.forEach(System.out::println);
如果你嘗試在 Lambda 中使用之後,又修改了 minAge——編譯器就會報錯。
表格:集合與串流的主要方法與 Lambda 表達式
| 集合/串流的方法 | 作用 | Lambda 表達式型別 | 範例 |
|---|---|---|---|
|
對每個元素執行 | |
|
|
依條件刪除元素 | |
|
|
對元素排序 | |
|
|
替換每個元素 | |
|
|
過濾串流 | |
|
|
轉換元素 | |
|
|
遍歷串流 | |
|
|
在串流中排序 | |
|
7. 常見錯誤
錯誤 1:Lambda 過長。 如果 Lambda 表達式裡已經有 5 行以上的程式碼、條件、迴圈與 try-catch——大概就該把這段程式抽出到獨立方法。Lambda 適合短邏輯。
錯誤 2:使用可變變數。 如果你嘗試在 Lambda 中修改外層方法的變數(例如計數器),編譯器不會允許。變數必須是 final 或實質上為 final。
錯誤 3:忘了集合/串流的方法不一定會改變原集合。 例如,stream().filter(...) 並不會改變原清單,而是回傳新的串流。若想得到集合——請使用 collect(Collectors.toList())。
錯誤 4:Lambda 表達式型別不相容。 如果方法需要的是 Comparator<T>,而你卻傳了一個只有一個參數(而不是兩個參數)的 Lambda——就會編譯失敗。
錯誤 5:巢狀 Lambda 導致可讀性下降。 如果你有一串 map、filter、forEach,而每個 Lambda 裡還包著另一個 Lambda——程式會變得難讀。在這種情況下,最好把運算拆成獨立步驟,或抽出成方法。
GO TO FULL VERSION