Stream API
Stream API 是 Java 8 引入的数据处理 API,用于以声明式方式完成过滤、映射、排序、分组、聚合等操作。它不是集合,也不存储数据,而是一条数据处理流水线。
# 1. Stream 模型
数据源
│
▼
中间操作 filter / map / sorted
│
▼
终止操作 collect / count / forEach
示例:
List<String> names = users.stream()
.filter(User::isActive)
.map(User::getName)
.toList();
这段代码表达的是:从用户列表中筛选活跃用户,再提取姓名。
# 2. Stream 不是集合
| 对比项 | Collection | Stream |
|---|---|---|
| 是否存储数据 | 是 | 否 |
| 是否可重复遍历 | 可以 | 通常只能消费一次 |
| 关注点 | 数据容器 | 数据处理流程 |
| 操作方式 | 增删改查 | 过滤、转换、聚合 |
Stream 被终止操作消费后不能再次使用。
Stream<User> stream = users.stream();
stream.count();
stream.toList(); // IllegalStateException
# 3. 中间操作与终止操作
中间操作是惰性的,不会立即执行。
Stream<User> stream = users.stream()
.filter(user -> {
System.out.println(user.getName());
return user.isActive();
});
没有终止操作时,filter 不会执行。
常见中间操作:
| 操作 | 作用 |
|---|---|
filter | 过滤 |
map | 映射 |
flatMap | 扁平化 |
distinct | 去重 |
sorted | 排序 |
limit | 截断 |
skip | 跳过 |
常见终止操作:
| 操作 | 作用 |
|---|---|
collect | 收集 |
toList | 转列表 |
count | 计数 |
forEach | 遍历消费 |
reduce | 归约 |
anyMatch | 任意匹配 |
allMatch | 全部匹配 |
findFirst | 找第一个 |
# 4. filter 与 map
List<Long> userIds = users.stream()
.filter(User::isActive)
.map(User::getId)
.toList();
流程:
User
│ filter active
▼
Active User
│ map getId
▼
Long id
filter 改变元素数量,map 改变元素形态。
# 5. flatMap
flatMap 用于把多层结构摊平。
List<String> tags = users.stream()
.flatMap(user -> user.getTags().stream())
.distinct()
.toList();
结构:
Stream<List<Tag>>
│ flatMap
▼
Stream<Tag>
适合处理一对多关系。
# 6. collect 与分组
按状态分组:
Map<OrderStatus, List<Order>> group = orders.stream()
.collect(Collectors.groupingBy(Order::getStatus));
统计数量:
Map<OrderStatus, Long> countMap = orders.stream()
.collect(Collectors.groupingBy(Order::getStatus, Collectors.counting()));
转 Map:
Map<Long, User> userMap = users.stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
如果 key 可能重复,必须提供合并函数:
Map<Long, User> userMap = users.stream()
.collect(Collectors.toMap(
User::getId,
Function.identity(),
(oldValue, newValue) -> newValue
));
# 7. reduce
reduce 用于归约。
int total = numbers.stream()
.reduce(0, Integer::sum);
流程:
初始值 0
│ + 1
▼
1
│ + 2
▼
3
│ + 3
▼
6
简单求和可优先使用基本类型流:
int total = users.stream()
.mapToInt(User::getAge)
.sum();
# 8. 并行流
users.parallelStream()
.map(this::calculate)
.toList();
并行流适合 CPU 密集、数据量较大、无共享状态的计算。
不适合:
- IO 阻塞。
- 数据量小。
- 修改共享集合。
- 依赖 ThreadLocal 上下文。
- 需要精确控制线程池。
并行流默认使用公共 ForkJoinPool,生产中要谨慎。
# 9. 常见坑
# 9.1 在 Stream 中做副作用
不推荐:
List<Long> ids = new ArrayList<>();
users.stream().forEach(user -> ids.add(user.getId()));
推荐:
List<Long> ids = users.stream()
.map(User::getId)
.toList();
# 9.2 toMap key 重复
Collectors.toMap 遇到重复 key 会抛异常,必须提供合并函数。
# 9.3 过度链式调用
Stream 链太长、Lambda 过复杂会降低可读性。复杂逻辑应抽方法。
# 10. 执行机制与性能边界
Stream 的执行可以理解为“声明一条流水线,终止操作触发执行”。中间操作并不会一个阶段一个阶段地创建完整集合,而是尽量把多个操作融合到一次遍历中。
数据元素 1
│ filter
│ map
│ collect
▼
结果容器
数据元素 2
│ filter
│ map
│ collect
▼
结果容器
这也是 Stream 在很多场景中既表达清晰又不会产生大量中间集合的原因。
| 操作类型 | 是否有状态 | 说明 |
|---|---|---|
filter | 无状态 | 每个元素独立判断 |
map | 无状态 | 每个元素独立转换 |
distinct | 有状态 | 需要记住已出现元素 |
sorted | 有状态 | 需要缓存并排序 |
limit | 短路 | 达到数量后可停止 |
anyMatch | 短路 | 找到满足元素即可停止 |
资深开发者使用 Stream 时会关注三件事:
- 这条链是否比普通循环更清晰。
- 是否引入隐藏副作用,例如修改外部集合、写数据库、发 RPC。
- 是否因为
sorted、distinct、groupingBy等操作带来额外内存和复杂度。
复杂业务规则可以保留 Stream 的骨架,但把规则抽成具名方法:
List<Order> riskyOrders = orders.stream()
.filter(this::isRiskyOrder)
.sorted(Comparator.comparing(Order::createdAt))
.toList();
这样既保留声明式流程,又能让规则单独测试。
# Tips 快问快答
Q:Stream 是集合吗? A:不是。Stream 是数据处理流水线,不存储数据。
Q:Stream 能重复使用吗? A:不能,被终止操作消费后通常不能再次使用。
Q:中间操作什么时候执行? A:遇到终止操作时才执行。
Q:filter 和 map 有什么区别? A:filter 过滤元素,map 转换元素。
Q:flatMap 解决什么问题? A:把嵌套流或多层集合摊平成一层。
Q:toMap key 重复会怎样? A:默认抛异常,需要提供合并函数。
Q:parallelStream 一定更快吗? A:不一定,要看数据量、任务类型和线程池影响。
Q:Stream 中适合做复杂业务吗? A:不适合,复杂逻辑应抽成具名方法。