Stream 操作是 Java 8+ 的大招。三個最常用的場景:合併多個 Map、把 List 轉成 Map、排序物件。一次整理出來,以後就 copy-paste 吧。
1. Stream 合併多個 Map
經常要把好幾個 Map 合起來,最直白的做法是逐個 put:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Map<String, Integer> map1 = new HashMap<>(); map1.put("Apple", 10); map1.put("Banana", 20);
Map<String, Integer> map2 = new HashMap<>(); map2.put("Orange", 15); map2.put("Pear", 25);
Map<String, Integer> map3 = new HashMap<>(); map3.put("Cherry", 30);
Map<String, Integer> merged = new HashMap<>(map1); merged.putAll(map2); merged.putAll(map3);
|
用 Stream 的做法更優雅:
1 2 3 4 5 6
| Map<String, Integer> merged = Stream.of(map1, map2, map3) .flatMap(map -> map.entrySet().stream()) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ));
|
邏輯:
Stream.of(map1, map2, map3) - 產生一個包含 3 個 Map 的 stream
.flatMap(map -> map.entrySet().stream()) - 把每個 Map 拆成 entry stream,再 flatten
- 結果是一堆 Map.Entry<String, Integer>
.collect(Collectors.toMap(...)) - 把 entries 轉回 Map
- Key extractor:
Map.Entry::getKey
- Value extractor:
Map.Entry::getValue
如果有重複的 key 怎麼辦
預設會爆 IllegalStateException。可以指定 merge 函數:
1 2 3 4 5 6 7
| Map<String, Integer> merged = Stream.of(map1, map2, map3) .flatMap(map -> map.entrySet().stream()) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue + newValue ));
|
或者只保留新的:
1 2 3 4 5
| .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> newValue ))
|
2. Stream 將 List 轉 Map
很常見的場景:有個物件列表,要轉成 Map,key 是 id,value 是物件本身(或某個欄位)。
基本用法
假設有個 User 物件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class User { private int id; private String name; private String email; public int getId() { return id; } public String getName() { return name; } public String getEmail() { return email; } }
List<User> users = Arrays.asList( new User(1, "Alice", "alice@example.com"), new User(2, "Bob", "bob@example.com"), new User(3, "Cathy", "cathy@example.com") );
|
轉 Map(key=id, value=User):
1 2 3 4 5 6 7 8
| Map<Integer, User> userMap = users.stream() .collect(Collectors.toMap( User::getId, Function.identity() ));
User user = userMap.get(1);
|
轉成 key=id, value=name
1 2 3 4 5 6 7
| Map<Integer, String> idToName = users.stream() .collect(Collectors.toMap( User::getId, User::getName ));
|
如果 List 裡有重複的 id
爆 exception。加上 merge function:
1 2 3 4 5 6
| Map<Integer, User> userMap = users.stream() .collect(Collectors.toMap( User::getId, Function.identity(), (existing, replacement) -> existing ));
|
或者只保留最後一個:
1 2 3 4 5
| .collect(Collectors.toMap( User::getId, Function.identity(), (existing, replacement) -> replacement ))
|
3. Stream 物件排序
單欄位排序
假設要按 name 排序:
1 2 3 4 5
| List<User> sorted = users.stream() .sorted(Comparator.comparing(User::getName)) .collect(Collectors.toList());
|
反序排列:
1 2 3 4 5
| List<User> sorted = users.stream() .sorted(Comparator.comparing(User::getName).reversed()) .collect(Collectors.toList());
|
多欄位排序
常見場景:先按部門排,再按名字排。用 thenComparing():
1 2 3 4
| List<User> sorted = users.stream() .sorted(Comparator.comparing(User::getDepartment) .thenComparing(User::getName)) .collect(Collectors.toList());
|
複雜點的例子:先按 department 升序,再按 salary 降序:
1 2 3 4
| List<User> sorted = users.stream() .sorted(Comparator.comparing(User::getDepartment) .thenComparing(Comparator.comparingInt(User::getSalary).reversed())) .collect(Collectors.toList());
|
特殊型態排序
Integer/Long:
1 2 3 4 5 6
| .sorted(Comparator.comparingInt(User::getAge)) .sorted(Comparator.comparingLong(User::getId))
.sorted(Comparator.comparingInt(User::getAge).reversed())
|
Double/Float:
1
| .sorted(Comparator.comparingDouble(User::getScore))
|
根據 Null 處理排序
如果有些欄位可能是 null,要特別處理:
1 2 3 4 5
| .sorted(Comparator.nullsFirst(Comparator.comparing(User::getDepartment)))
.sorted(Comparator.nullsLast(Comparator.comparing(User::getDepartment)))
|
實戰例子
把這三個操作組合起來:
例子 1:合併多個 user list,去掉重複,按 id 排序
1 2 3 4 5 6 7 8 9
| List<User> mergedUsers = Stream.of(users1, users2, users3) .flatMap(List::stream) .collect(Collectors.collectingAndThen( Collectors.toCollection(() -> new TreeSet<>(Comparator.comparingInt(User::getId))), ArrayList::new )) .stream() .sorted(Comparator.comparingInt(User::getId)) .collect(Collectors.toList());
|
例子 2:List 轉 Map,然後排序值
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Map<String, Integer> scoreMap = users.stream() .collect(Collectors.toMap( User::getName, User::getScore, (s1, s2) -> Math.max(s1, s2) ));
List<Map.Entry<String, Integer>> sorted = scoreMap.entrySet().stream() .sorted(Map.Entry.<String, Integer>comparingByValue().reversed()) .collect(Collectors.toList());
sorted.forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
|
例子 3:按多欄位排序,然後轉成 Map
1 2 3 4 5 6 7 8 9
| Map<Integer, User> sortedUsers = users.stream() .sorted(Comparator.comparing(User::getDepartment) .thenComparing(User::getName)) .collect(Collectors.toMap( User::getId, Function.identity(), (a, b) -> a, LinkedHashMap::new ));
|
注意最後一個參數 LinkedHashMap::new,這樣 Map 會保持 stream 的排序順序(HashMap 不會)。
效能提示
- 合併 Map - 如果 Map 超級大,考慮直接 putAll 而不是 stream
- List 轉 Map - 如果 List 有上百萬元素,stream 會有開銷,直接 for loop 可能更快
- 排序 - Stream sorted() 是 O(n log n),大資料集要小心
但大多數時候,stream 的可讀性遠勝效能損失。除非是效能熱點,否則就用 stream 吧。
總結
三大絕招:
1 2 3 4 5 6 7 8 9 10
| Stream.of(map1, map2).flatMap(m -> m.entrySet().stream()) .collect(Collectors.toMap(E::getKey, E::getValue))
list.stream().collect(Collectors.toMap(User::getId, Function.identity()))
list.stream().sorted(Comparator.comparing(User::getName).thenComparing(User::getAge)) .collect(Collectors.toList())
|
組合起來用,就沒有搞不定的集合操作。
你的鼓勵將被轉換為我明天繼續加班的動力(真的)。 ❤️