Java 8 引入 Stream API,通過聚合操作實現集合的聲明式處理。本文系統講解 Stream 創建、中間/終端操作、並行流、收集器、最佳實踐。
Stream 創建入口
// Collection 直接創建
List list = ...;
Stream stream = list.stream(); // 串行
Stream pStream = list.parallelStream(); // 並行
// 數組創建
Stream aStream = Arrays.stream(array);
Stream arrStream = Arrays.stream(intArray); // 基本類型流
// 靜態工廠
Stream.of("a", "b", "c");
Stream.iterate(0, n -> n + 2).limit(10); // 無限流
Stream.generate(Math::random).limit(5); // 無限流
// 基本類型專用流
IntStream.range(0, 100);
LongStream.of(1L, 2L, 3L);
DoubleStream.generate(() -> Math.random());
操作分類:管道模型
數據源 → 中間操作 (0..N) → 終端操作 (1)
惰性求值 觸發執行
中間操作:轉換與過濾
| 操作 | 功能 | 特點 |
| filter(Predicate) | 過濾元素 | 保留滿足條件者 |
| map(Function) | 一對一轉換 | 類型可變 T→R |
| flatMap(Function) | 一對多扁平化 | 流扁平化 Stream> |
| distinct() | 去重 | 基於 equals,有狀態 |
| sorted() / sorted(Comparator) | 排序 | 有狀態,需緩存所有元素 |
| peek(Consumer) | 側效觀察 | 調試用,不改變流 |
| limit(n) / skip(n) | 截取/跳過 | 短路操作 |
終端操作:產生結果
| 分類 | 操作 | 返回值 |
| 遍歷/匹配 | forEach(Consumer) | void |
| forEachOrdered(Consumer) | void (保序) |
| anyMatch/allMatch/noneMatch(Predicate) | boolean (短路) |
| findFirst/findAny() | Optional (短路) |
| 歸約/聚合 | reduce(BinaryOperator) | Optional |
| reduce(identity, BinaryOperator) | T |
| collect(Collector) | R (可變容器) |
| count() | long |
| 數值計算 | sum/average/min/max() | 基本類型/Optional |
| summaryStatistics() | IntSummaryStatistics 等 |
| toArray() | Object[] |
實戰範例:Person 集合處理
List persons = ...;
// 1. 篩選成年男性,按年齡降序,取前 3 名字
List top3 = persons.stream()
.filter(p -> p.getAge() >= 18)
.filter(p -> p.getGender() == Sex.MALE)
.sorted(Comparator.comparing(Person::getAge).reversed())
.limit(3)
.map(Person::getName)
.collect(Collectors.toList());
// 2. 分組統計:性別 → 平均年齡
Map avgAge = persons.stream()
.collect(Collectors.groupingBy(Person::getGender,
Collectors.averagingInt(Person::getAge)));
// 3. 分區:成年/未成年 → 名字列表
Map> partitioned = persons.stream()
.collect(Collectors.partitioningBy(p -> p.getAge() >= 18,
Collectors.mapping(Person::getName, Collectors.toList())));
// 4. 並行處理大數據集
long adultCount = persons.parallelStream()
.filter(p -> p.getAge() >= 18)
.count();
收集器進階
// 自定義收集器:字符串連接 (已內置 Collectors.joining)
Collector joining = Collector.of(
StringBuilder::new,
(sb, s) -> sb.append(s),
(sb1, sb2) -> sb1.append(sb2),
StringBuilder::toString
);
// 分組 + 下游收集器
Map> byGender = persons.stream()
.collect(Collectors.groupingBy(Person::getGender,
Collectors.mapping(p -> p.getName(), Collectors.toList())));
並行流注意事項
- 無副作用: Lambda 必須純函數,不修改共享狀態
- 數據源可分割: ArrayList/數組分割快,LinkedList 分割慢
- 計算密集型受益: CPU 密集任務加速明顯,IO 密集反而可能變慢
- 公共 ForkJoinPool: 所有並行流共享,長任務可能阻塞其他並行流
Stream vs Iterator
| 特性 | Iterator | Stream |
| 控制權 | 開發者手動 next() | 框架內部自動迭代 |
| 執行模式 | 僅順序 | 可並行 |
| 操作風格 | 命令式 | 聲明式/函數式 |
| 數據源修改 | 可邊遍歷邊修改 (風險) | 不修改源,產生新流 |
| 惰性求值 | 無 | 中間操作惰性,終端觸發 |
避坑指南
- ❌ 重複使用流:流只能消費一次,需重新獲取
- ❌ 在流中修改底層集合:
ConcurrentModificationException
- ❌ 基本類型流裝箱開銷:優先用
IntStream/LongStream/DoubleStream
- ❌ 並行流處理有序敏感操作:
findFirst()、limit() 會強制保序降低並行度
- ✅ 善用
Collectors 內置收集器,減少自定義
- ✅ 大數據集優先
parallelStream(),小數據集串行更快