[Java 8 Lambda] java.util.stream 简介

本文深入解析Java Stream API的包结构及核心组件,包括BaseStream、Stream接口、IntStream等特定类型Stream,以及Collect接口和Collectors类的作用。探讨了如何利用这些组件实现高效的并行计算。

包结构如下所示:




这个包的结构很简单,类型也不多。

 

BaseStream接口

所有Stream接口类型的父接口,它继承自AutoClosable接口,定义了一些所有Stream都具备的行为。

 

因为继承自AutoClosable接口,所以所有的Stream类型都可以用在Java 7中引入的try-with-resource机制中,以达到自动关闭资源的目的。实际上,只有当Stream是通过SocketFiles IO等方式创建的时候,才需要关闭它。对于来自于CollectionsArraysStream,是不需要关闭的。

 

Stream接口

定义了众多Stream应该具有的行为。

最典型的比如filter方法族,map方法族以及reduce方法族,这三个方法是FunctionalProgramming的标志。典型的Map-Filter-Reduce模式便是依靠这三个操作来定义的。

 

与此同时,Stream接口还定义了一些用于创建Streamstatic方法,创建的Stream可以是有限的,也可以是无限的。有限的很好理解,而无限Stream是一个新概念,通过generate方法或者iterate方法实现。

 

IntStream, LongStream 以及 DoubleStream 接口

基于原生类型int, long以及doubleStream。提供了众多类型相关的操作。

典型的例如,sum方法,min/max方法,average方法等。这些方法都是Reduce操作的具体实现。

 

Collect接口

对于Reduce操作的抽象。此接口中定义了常用的Reduce操作。

其中定义的Reduce操作可以通过串行或者并行的方式进行实现。BaseStream接口中的parallelsequentialunordered方法提供的高层API使并发程序设计变得非常简洁。

毕竟,Map-Filter-Reduce模式的灵魂就在于并行计算。

 

Collectors

提供了众多可以直接使用的Reduce操作。

典型的比如groupingBy以及partitioningBy操作。它们都可以通过串行或者并行的方式进行实现。比如,groupingByConcurrent会使用并行的方式进行grouping操作。

 

StreamSupport

提供了底层的一些用于操作Stream的方法,如果不需要创建自己的Stream,一般不需要使用它。


你遇到的异常: ``` java.lang.IllegalStateException: Duplicate key 11111 at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133) ... ``` 说明你在使用 `Collectors.toMap()` 时,**有重复的键(key)出现**,而没有提供“合并策略”(merge function),导致系统抛出异常。 --- ### 🔍 异常原因分析 假设你的代码中有类似如下语句: ```java Map<String, String> userMap = userIdsToQuery.stream() .collect(Collectors.toMap( id -> id, id -> queryUserNameById(id) // 比如从数据库或服务查名字 )); ``` 当多个用户 ID 查询返回相同的 key(比如都为 `"11111"`),或者原始数据中本身就存在重复 ID,就会触发这个错误。 但在你的情况中,更可能的是: 👉 **`userIdsToQuery` 集合本身没问题(Set 去重了),但查询结果中出现了重复的用户 ID 作为 map 的 key。** 例如: - 数据库返回的用户列表里有两个用户的 ID 都是 `11111`; - 或者你在 `queryUserNameMapByIds` 内部使用了 `toMap()` 且未处理重复 key。 --- ### ✅ 正确解决方案 #### ✅ 方案一:在 `toMap` 中添加合并策略(推荐) 如果你不能保证 key 不重复,就必须提供一个合并函数来决定如何处理冲突: ```java Map<String, String> userMap = userList.stream() .collect(Collectors.toMap( User::getId, // key: 用户ID User::getName, // value: 用户名 (name1, name2) -> { // 当key冲突时,选择哪一个value // 可以选择第一个、第二个,或拼接等 return name1; // 保留第一个 } )); ``` > `(name1, name2) -> name1` 表示遇到重复 key 时保留第一个值。 --- #### ✅ 方案二:先去重再 collect ```java Map<String, String> userMap = userList.stream() .distinct() // 需重写 User.equals/hashCode,否则无效 .collect(Collectors.toMap(User::getId, User::getName)); ``` 但注意:`distinct()` 是基于对象引用或 `equals()` 方法的,如果对象不同但 ID 相同,不会自动去重。所以建议结合 `groupingBy` 或直接用 `toMap` + 合并策略。 --- #### ✅ 方案三:使用 `groupingBy` + 取第一个(最安全) ```java Map<String, String> userMap = userList.stream() .collect(Collectors.groupingBy( User::getId )) .entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, e -> e.getValue().get(0).getName() // 取每组第一个用户的名字 )); ``` 这种方式能明确处理重复 key,并可记录日志警告。 --- ### 🛠️ 应用于你的场景 回到你之前的优化代码: ```java Map<String, String> userMap = drcpPoAlarmService.queryUserNameMapByIds(userIdsToQuery); ``` 你应该确保这个方法内部不会因重复 key 报错。比如它的实现可能是: ```java public Map<String, String> queryUserNameMapByIds(Collection<String> userIds) { if (userIds == null || userIds.isEmpty()) { return Collections.emptyMap(); } List<User> users = userMapper.selectByIds(userIds); // 查询数据库 return users.stream() .collect(Collectors.toMap( User::getId, User::getName, (name1, name2) -> name1 // 处理重复 ID,保留第一个 )); } ``` 或者如果你怀疑数据库有问题,可以加日志排查: ```java long count = users.size(); long distinctCount = users.stream().map(User::getId).distinct().count(); if (count != distinctCount) { log.warn("用户列表中存在重复ID: total={}, distinct={}", count, distinctCount); } ``` --- ### ❌ 错误做法(会导致此异常) ```java // 危险!没有 merge function Map<String, String> map = list.stream() .collect(Collectors.toMap(User::getId, User::getName)); // 一旦有重复ID就炸 ``` --- ### ✅ 总结 | 问题 | 解决方案 | |------|----------| | `Duplicate key 11111` | 使用 `toMap(keyMapper, valueMapper, mergingFunction)` | | 数据源有重复 ID | 在收集时用 `(v1, v2) -> v1` 保留第一个 | | 想排查重复 | 用 `groupingBy` 或统计前后数量差异 | --- ###
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值