如何用Java Stream Collectors生成带计数的总和映射
需求实现与代码校验
现有类定义
Result类
class Result { String code; String description; Duration value; }
Product类
class Product { String code; String description; }
Total类
class Total { Duration total; long count; }
需求描述
给定List<Result>列表,生成Map<Product, Total>映射:以Result的code和description组成的Product为键,对应的Total存储该分组下所有Result的value总和(通过Duration.plus方法累加),以及该分组内Result的数量。
用户代码校验
你编写的代码如下:
Map <Product, Total> map = results.stream() .collect(Collectors.groupingBy(p -> new Product(p.getCode(), p.getDescription()), Collectors.collectingAndThen(Collectors.toList(), list -> { Duration total = list.stream().map(Result::getValue).reduce(Duration.ZERO, Duration::plus); long count = list.size(); return new Total(total, count); })));
存在的问题
- 核心错误:
Product类未重写equals和hashCode方法。groupingBy依赖键的相等性判断,默认的equals是比较对象内存地址,即使两个Product的code和description完全相同,也会被视为不同键,导致分组结果完全错误。 - 效率问题:先将分组结果转为
List再二次遍历流求和,属于冗余操作,会降低处理效率。 - 依赖问题:代码中调用了
p.getCode()和p.getDescription(),但你给出的Result类是直接暴露成员变量,若没有对应的getter方法,会编译失败。
正确实现方案
第一步:修复Product类
必须给Product添加构造方法,并基于code和description重写equals和hashCode:
import java.util.Objects; class Product { String code; String description; public Product(String code, String description) { this.code = code; this.description = description; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Product product = (Product) o; return Objects.equals(code, product.code) && Objects.equals(description, product.description); } @Override public int hashCode() { return Objects.hash(code, description); } }
第二步:优化流式收集代码
方案1:Java 12+ 用Collectors.teeing(推荐)
teeing可以同时收集两个结果(总和与数量),再合并为Total,代码简洁高效:
Map<Product, Total> map = results.stream() .collect(Collectors.groupingBy( result -> new Product(result.code, result.description), Collectors.teeing( Collectors.reducing(Duration.ZERO, result -> result.value, Duration::plus), Collectors.counting(), Total::new ) ));
方案2:Java 8 兼容版本
如果使用Java 8,没有teeing,可以用reducing直接合并Total,避免二次流处理:
Map<Product, Total> map = results.stream() .collect(Collectors.groupingBy( result -> new Product(result.code, result.description), Collectors.reducing( new Total(Duration.ZERO, 0L), result -> new Total(result.value, 1L), (total1, total2) -> new Total(total1.total.plus(total2.total), total1.count + total2.count) ) ));
补充说明
- 若
Result类的成员变量是私有访问权限,需要添加对应的getter方法(比如getCode()、getDescription()、getValue()),否则无法直接访问成员变量。 Total类建议添加构造方法,方便直接实例化:
class Total { Duration total; long count; public Total(Duration total, long count) { this.total = total; this.count = count; } }
内容的提问来源于stack exchange,提问作者res1
相关产品推荐
相关产品推荐

