Java 8 Stream如何按多字段分组并对views、shares字段求和
问题分析
原有实现的核心问题是分组维度不全:
- 仅以
publicationID作为groupingBy的分组键,没有将publicationName、locale纳入分组规则,会把同ID不同地区/名称的记录错误合并,自然无法得到按三个字段拆分的结果 - 直接对分组后的流调用
reduce后取Optional::get,在分组为空的场景会抛出异常,鲁棒性不足
正确实现方案
要实现多字段联合分组,核心是构造包含全部分组维度的复合键,再对每个分组内的views、shares字段求和即可。
方案1:Java 16+ 推荐写法(最简洁安全)
Java 16引入的record是不可变类,默认按所有字段实现equals()和hashCode(),非常适合作为分组键,不需要手动编写重写逻辑:
import static java.util.stream.Collectors.*; import java.util.List; // 定义三字段复合分组键 record GroupKey(String publicationName, String publicationID, String locale) {} public List<PublicationDTO> aggregatePublications(List<PublicationDTO> publicationDTOS) { return publicationDTOS.stream() .collect(groupingBy( // 按三个字段组合成复合键分组 dto -> new GroupKey(dto.publicationName, dto.publicationID, dto.locale), // 分组后对views、shares求和,构造新的DTO collectingAndThen( toList(), groupList -> { PublicationDTO sample = groupList.get(0); int totalViews = groupList.stream().collect(summingInt(dto -> dto.views)); int totalShares = groupList.stream().collect(summingInt(dto -> dto.shares)); return new PublicationDTO( sample.publicationName, sample.publicationID, sample.locale, totalViews, totalShares ); } ) )) .values() .stream() .collect(toList()); }
方案2:Java 8 兼容写法
如果使用Java 8没有record,可以用Arrays.asList作为复合键(List的equals和hashCode默认按元素内容判断,可作为Map键使用),不需要额外定义类:
import static java.util.stream.Collectors.*; import java.util.Arrays; import java.util.List; public List<PublicationDTO> aggregatePublicationsJava8(List<PublicationDTO> publicationDTOS) { return publicationDTOS.stream() .collect(groupingBy( // 三个字段按固定顺序放入列表作为复合键 dto -> Arrays.asList(dto.publicationName, dto.publicationID, dto.locale), // 分组内累加views和shares collectingAndThen( reducing((dto1, dto2) -> new PublicationDTO( dto1.publicationName, dto1.publicationID, dto1.locale, dto1.views + dto2.views, dto1.shares + dto2.shares )), // groupingBy生成的分组至少有1个元素,此处get是安全操作 Optional::get ) )) .values() .stream() .collect(toList()); }
结果验证
以上两种写法运行你提供的测试数据,都会输出完全符合预期的结果:
List.of( new PublicationDTO("Name1", "name1", "CA", 15, 7), new PublicationDTO("Name2", "name2", "CA", 2, 3), new PublicationDTO("Name2", "name2", "US", 6, 3) );
注意:如果自定义普通类作为分组键,必须正确重写类的
equals()和hashCode()方法,否则会出现同维度数据被分到不同组的问题。
内容的提问来源于stack exchange,提问作者yaroslav96
相关产品推荐
相关产品推荐

