使用
Collectors.groupingBy是正确的方法,但不要使用单个参数版本来创建每个组的所有项目列表,而应使用两个arg版本,后者使用另一个参数版本Collector来确定如何汇总每个组的元素。
当您要汇总元素的单个属性或仅计算每个组中元素的数量时,这特别平滑:
数数:
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));
在您要聚合多个属性的情况下,指定一种自定义归约操作(如此答案中所建议的那样)是正确的方法,但是,您可以在分组操作期间执行归约操作,因此无需将整个数据收集到Map<…,List>执行还原之前的a :
(我假设您
import static java.util.stream.Collectors.*;现在使用…)
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提取流元素的属性并创建/返回新的关键对象。但这要求键属性具有适当的holder类(而Java没有通用的Tuple类)。
但是还有另一种选择。通过使用三参数形式,groupingBy我们可以为实际Map实现指定供应商,该供应商将确定键的相等性。通过使用带有比较器比较多个属性的排序映射,我们无需其他类即可获得所需的行为。我们只需要注意不要使用比较器忽略的关键实例中的属性,因为它们只有任意值:
list.stream().collect(groupingBy(Function.identity(), ()->new TreeMap<>( // we are effectively grouping by [id, actualCost] Comparator.<Foo,Integer>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));



