Java 8 Stream嵌套Map分组重复键冲突及合并方案问询
这个问题我碰到过不少次了——用Stream的toMap处理重复键的时候确实容易踩坑,尤其是你需要把重复键对应的多个元素收集成列表的时候,toMap默认的行为根本满足不了需求。
问题根源
你原来的代码里用toMap(Status::getProcessedDate, Function.identity()),当同一个Organisation下出现相同的processedDate时,toMap无法处理重复键,直接抛出IllegalStateException。哪怕你添加了合并函数(比如(v1, v2) -> v1),也只会保留其中一个元素,而不是把所有同日期的对象合并到列表中,这和你的预期不符。
正确解决方案:嵌套使用groupingBy
groupingBy是专门用来按键分组并收集元素的收集器,嵌套使用它正好能实现你想要的Map<Organisation, Map<LocalDate, List<Status>>>结构:
private Map<Organisation, Map<LocalDate, List<Status>>> getSummaries(final List<Status> summaries) { return summaries.stream() .collect(Collectors.groupingBy( Status::getOrganisation, Collectors.groupingBy(Status::getProcessedDate) )); }
代码解释
- 外层
groupingBy(Status::getOrganisation):先将所有Status对象按所属的Organisation进行第一层分组; - 内层
groupingBy(Status::getProcessedDate):对每个Organisation下的Status,再按processedDate进行第二层分组,默认会将同日期的所有对象收集到List<Status>中,完全匹配你期望的嵌套Map结构。
示例验证
用你给出的测试数据:
Summary(ProcesseDate=2020-01-30, Organisation=ABC, status=OK, statusCount=5)
Summary(ProcesseDate=2020-01-30, Organisation=ABC, status=FAILED, statusCount=2)
Summary(ProcesseDate=2020-01-30, Organisation=APPLE, status=OK, statusCount=5)
Summary(ProcesseDate=2020-01-30, Organisation=APPLE, status=REJECTED, statusCount=5)
生成的Map结构会是:
- 键
ABC对应的值:{2020-01-30: [ABC的两个Summary对象]} - 键
APPLE对应的值:{2020-01-30: [APPLE的两个Summary对象]}
可选扩展
如果需要对收集到的列表做自定义处理(比如排序、过滤),可以在内层groupingBy后指定自定义收集器,例如:
Collectors.groupingBy( Status::getProcessedDate, Collectors.mapping(Function.identity(), Collectors.toList()) )
不过默认情况下groupingBy已经使用toList()作为默认收集器,所以简化写法完全够用。
内容的提问来源于stack exchange,提问作者M06H

