Map(映射)與 Reduce(歸約)是函數式編程兩大基石。Java 8 通過 map/mapToInt 等中間操作實現 Map,通過 reduce 終端操作實現 Reduce。本文從基礎求和到極值查找、字符串拼接,全方位演示歸約威力。
// 提取名字長度 → IntStream
IntStream lengths = friends.stream().mapToInt(String::length);
// 基本類型流專用聚合
int total = lengths.sum(); // 總和
int maxLen = lengths.max().orElse(0);
double avg = lengths.average().orElse(0);
// 1. 無初始值,返回 Optional
Optional<T> reduce(BinaryOperator<T> accumulator)
// 2. 有初始值,返回 T
T reduce(T identity, BinaryOperator<T> accumulator)
// 3. 並行專用:identity + accumulator + combiner
U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner)
Optional<String> longest = friends.stream()
.reduce((name1, name2) ->
name1.length() >= name2.length() ? name1 : name2);
longest.ifPresent(name -> System.out.println("Longest: " + name));
// Longest: Brian
執行流程:Brian vs Nate → Brian → Brian vs Neal → Brian → ... 累加器不斷攜帶「當前最長」向後比較。
String defaultName = "Steve";
String result = friends.stream()
.reduce(defaultName, (n1, n2) -> n1.length() >= n2.length() ? n1 : n2);
// 若 friends 為空,直接返回 "Steve"
// 手寫 reduce 拼接
String joined = friends.stream()
.reduce("", (a, b) -> a + (a.isEmpty() ? "" : ", ") + b);
// Brian, Nate, Neal, Raju, Sara, Scott
// 生產級:Collectors.joining
String joined = friends.stream().collect(Collectors.joining(", "));
collect(Collector) 是 reduce 的泛化,分離「累加容器創建、元素累加、容器合併、最終轉換」四步,支援並行。常用收集器:
// 轉 List/Set
.toList() / .toSet()
// 分組
.groupingBy(Person::getDept)
// 多級分組 + 下游聚合
.groupingBy(Person::getDept,
groupingBy(Person::getGender, averagingDouble(Person::getSalary)))
// 字符串拼接
.joining(", ", "[", "]") // [Brian, Nate, ...]
| 場景 | 推薦 | 理由 |
|---|---|---|
| 簡單聚合(sum/max) | 基本類型流 .sum()/.max() | 內部優化,避免裝箱 |
| 複雜對象歸約 | reduce(identity, acc, combiner) | 並行友好,combiner 必須滿足結合律 |
| 收集到集合/Map | collect(Collectors.xxx) | 專用收集器高效合併 |
| 字符串拼接 | Collectors.joining | 內部用 StringJoiner,避免 O(n²) |
map 負責「形狀轉換」,reduce 負責「值累積」。掌握三種 reduce 重載與 Collector 四步法,能優雅解決從求和到複雜分組聚合的所有歸約問題。下一篇將探討 StringJoiner 與 Collectors.joining 的字符串拼接最佳實踐。