Java Stream 与函数式编程
更新时间:2026-08-25。本文是
languages/java/主题第 ④ 篇。从 Java 8 起,Java 支持函数式编程——用 lambda + Stream 以"声明式"方式处理集合,代码更短、更接近意图,还能轻松并行。
本文要回答的问题
- lambda 和函数式接口是什么?
::方法引用怎么用? - Stream 管道(map/filter/reduce)怎么组织?
- 并行流一定更快吗?有什么陷阱?
一、lambda 与函数式接口
java
// 传统匿名类
Runnable r1 = new Runnable() { public void run() { System.out.println("hi"); } };
// lambda 写法
Runnable r2 = () -> System.out.println("hi");函数式接口:只有一个抽象方法的接口(Runnable、Comparator、Function、Predicate),lambda 就是它的实例。
java
List<String> names = List.of("tom", "jerry", "bob");
names.sort((a, b) -> a.length() - b.length()); // lambda
names.forEach(System.out::println); // 方法引用 ::二、Stream 管道:三阶段
Stream 编程是"源 → 中间操作 → 终止操作"的管道:
java
int total = List.of(1, 2, 3, 4, 5, 6)
.stream()
.filter(n -> n % 2 == 0) // 中间:过滤偶数
.mapToInt(n -> n * 10) // 中间:映射
.sum(); // 终止:求和
| 阶段 | 特点 | 例子 |
|---|---|---|
| 源 | 提供数据 | list.stream()、Arrays.stream() |
| 中间操作 | 惰性(不触发执行),可链式 | filter、map、sorted、distinct、limit |
| 终止操作 | 触发执行,产生结果 | collect、forEach、reduce、count |
关键:中间操作不执行,只有遇到终止操作才真正跑——这是 Stream 与普通循环的本质区别(可优化为一次遍历)。
三、常用操作速查
| 操作 | 类型 | 作用 | 示例 |
|---|---|---|---|
filter | 中间 | 过滤 | filter(n -> n>0) |
map | 中间 | 映射 | map(s -> s.toUpperCase()) |
flatMap | 中间 | 摊平嵌套 | flatMap(List::stream) |
sorted | 中间 | 排序 | sorted() |
distinct | 中间 | 去重 | distinct() |
limit/skip | 中间 | 截断 | limit(10) |
collect | 终止 | 聚成集合 | collect(Collectors.toList()) |
reduce | 终止 | 归约 | reduce(0, Integer::sum) |
count/max/min | 终止 | 聚合 | count() |
3.1 用 Collectors 收集
java
List<String> list = names.stream().map(String::toUpperCase).collect(Collectors.toList());
Map<Integer,List<String>> byLen = names.stream().collect(Collectors.groupingBy(String::length));
String joined = names.stream().collect(Collectors.joining(", "));四、Optional:消灭空指针
java
Optional<String> opt = Optional.ofNullable(maybeNull);
String result = opt.orElse("默认值"); // 兜底
opt.map(String::toUpperCase).orElseThrow(() -> new RuntimeException());| 方法 | 作用 |
|---|---|
ofNullable(x) | 包装可能为 null 的值 |
orElse(default) | 为空返回默认 |
orElseGet(supplier) | 为空惰性构造(省开销) |
map/filter | 链式转换 |
orElseThrow() | 为空抛异常 |
本站视角:orElse(default) 会立即求值默认值,orElseGet() 惰性——orElseGet(() -> expensive()) 可避免不必要的开销。
五、并行流与性能陷阱
5.1 并行流
java
list.parallelStream() // 底层用 ForkJoinPool 并行
.filter(...).map(...).collect(...);5.2 不是越快越好
| 陷阱 | 说明 |
|---|---|
| 数据量小 | 线程调度开销 > 并行收益 |
| 有状态/有副作用操作 | forEach 里改共享变量 → 线程不安全 |
| 顺序依赖 | sorted/limit 难并行 |
| 装箱开销 | Integer 对象内存与 GC |
| 共享资源竞争 | 并行反更慢 |
本站建议:并行流只用于数据量大、无共享状态、CPU 密集的场景;否则用顺序流。基准测试方法见 基准测试方法论。
六、函数式 vs 传统循环
| 对比 | 传统 for | Stream |
|---|---|---|
| 表达 | 过程式(怎么做) | 声明式(做什么) |
| 可读性 | 模板代码多 | 意图清晰 |
| 并行 | 手动 | .parallelStream() 一行 |
| 调试 | 断点直白 | 管道中间态需 peek |
| 性能 | 直接 | 有中间对象/装箱开销 |
七、与本站主线衔接
- 性能:Stream 的装箱(int↔Integer)、中间集合开销是真实成本;高吞吐场景与手写循环对比见 基准测试。
- 函数式范式:Stream 对应函数式编程的 map/filter/reduce,见 OOP vs 函数式。
- 后端:Spring 中大量用 Stream/Optional 处理数据,见 backend/。
一句话总结
Stream 函数式编程 = lambda 表达函数、管道"源→中间→终止"声明式处理集合、Optional 消灭空指针、parallelStream 一行并行(但有大/无状态/CPU 密集前提);代码更短、意图更清晰,性能取舍需基准测试。
上一节:并发编程与 JUC 上一篇:Java 核心主题总纲