Stream 不是“更短的 for 循环”,而是描述数据如何从输入经过变换得到结果。可读的流水线让每一步只做一件事,并把 数据库写入、日志等副作用放到边界之外。
Table of contents
Open Table of contents
写一条可测试的数据管道
import java.math.BigDecimal;
import java.util.*;
record 订单(String customerId, BigDecimal amount, boolean paid) {}
static Map<String, BigDecimal> 已支付金额按客户汇总(List<订单> orders) {
return orders.stream()
.filter(订单::paid)
.collect(java.util.stream.Collectors.groupingBy(
订单::customerId,
java.util.stream.Collectors.reducing(
BigDecimal.ZERO, 订单::amount, BigDecimal::add)));
}
filter 是中间操作,collect 是终止操作。Stream 具有惰性求值(Lazy Evaluation):在终止操作出现前,中间操作
通常不会执行。一个 Stream 只能消费一次;需要再次处理时重新从数据源创建。
Lambda 的本质
Lambda 表达式是函数式接口实例的简写。函数式接口只有一个抽象方法,例如 Predicate<T> 接收值并返回布尔结果,
Function<T,R> 把一种值转换成另一种值。
java.util.function.Predicate<订单> 已支付 = 订单::paid;
java.util.function.Function<订单, BigDecimal> 取金额 = 订单::amount;
方法引用 订单::paid 并不天然比 Lambda 更好;只有当名称能准确表达意图时使用。复杂规则提取成命名方法,避免在
流水线里塞入大段条件。
把副作用移出流水线
**副作用(Side Effect)**是函数除返回值外对外部状态产生的变化,例如修改共享列表、写数据库或发送消息。
var ids = new ArrayList<String>();
orders.stream().filter(订单::paid).forEach(o -> ids.add(o.customerId())); // 不推荐
改成返回新值:
var ids = orders.stream().filter(订单::paid).map(订单::customerId).toList();
这样更容易测试,也不会在切换并行执行后产生数据竞争。
不要随手调用 parallel
并行流使用共享的 ForkJoinPool。它可能适合数据量足够大、任务 CPU 密集、每项成本相近且没有副作用的场景。数据库 调用、HTTP 请求和小集合通常不适合:阻塞会占住公共线程,拆分与合并开销可能大于收益。
决定并行前应使用 JMH 等基准工具测量顺序和并行版本,并记录数据规模、预热、CPU 核数与分位耗时。不能用一次
System.nanoTime() 得出生产结论。
可读性停止线
流水线超过约五六步、需要多次维护状态或包含复杂分支时,命名中间结果或改回普通循环往往更清楚。Stream 是工具, 不是代码风格考核。
下一步
阅读文件、网络与 NIO 基础,理解数据管道与真实 I/O 的边界。