使用Collectors.groupingBy是正确的方法,而不是使用单个参数版本,它将为每个组创建一个所有项目的列表,您应该使用
the two arg version,它需要另一个收集器来确定如何聚合每个组的元素.
当您要聚合元素的单个属性或仅计算每个元素的数量时,这一点尤其顺利:
>计数:
list.stream()
.collect(Collectors.groupingBy(foo -> foo.id, Collectors.counting()))
.forEach((id,count)->System.out.println(id+"\t"+count));
>总结一个属性:
list.stream()
.collect(Collectors.groupingBy(foo -> foo.id,
Collectors.summingInt(foo->foo.targetCost)))
.forEach((id,sumTargetCost)->System.out.println(id+"\t"+sumTargetCost));
在您想要聚合多个属性指定自定义缩减操作的情况下,like suggested in this answer是正确的方法,但是您可以在分组操作期间执行缩减权限,因此无需将整个数据收集到Map< ... ,列表>执行裁减前:
(我假设你使用import static java.util.stream.Collectors.*; now …)
list.stream().collect(groupingBy(foo -> foo.id, collectingAndThen(reducing(
(a,b)-> new Foo(a.id, a.ref, a.targetCost+b.targetCost, a.actualCost+b.actualCost)),
Optional::get)))
.forEach((id,foo)->System.out.println(foo));
为了完整性,这里是一个超出您的问题范围的问题的解决方案:如果您想要GROUP BY多列/属性?
跳过程序员头脑的第一件事是使用groupingBy来提取流元素的属性并创建/返回一个新的关键对象.但是这需要关键属性的适当的持有者类(而Java没有通用的Tuple类).
但还有一个选择.通过使用three-arg form of groupingBy,我们可以为实际的Map实现指定一个供应商,这将确定关键的平等.通过使用具有比较多个属性的比较器的排序映射,我们获得所需的行为,而不需要额外的类.我们只需要注意不要使用比较器忽略的关键实例的属性,因为它们只有任意的值:
list.stream().collect(groupingBy(Function.identity(),
()->new TreeMap<>(
// we are effectively grouping by [id, actualCost]
Comparator.comparing(foo->foo.id).thenComparing(foo->foo.actualCost)
), // and aggregating/ summing targetCost
Collectors.summingInt(foo->foo.targetCost)))
.forEach((group,targetCostSum) ->
// take the id and actualCost from the group and actualCost from aggregation
System.out.println(group.id+"\t"+group.actualCost+"\t"+targetCostSum));