java - 像在 SQL 中使用 Java lambda 一样对对象进行分组和求和?

标签 java lambda java-8 java-stream

我有一个类 Foo ,其中包含这些字段:

id:int / name;String / targetCost:BigDecimal / actualCost:BigDecimal

我得到了这个类的对象的数组列表。例如:

new Foo(1, "P1", 300, 400), 
new Foo(2, "P2", 600, 400),
new Foo(3, "P3", 30, 20),
new Foo(3, "P3", 70, 20),
new Foo(1, "P1", 360, 40),
new Foo(4, "P4", 320, 200),
new Foo(4, "P4", 500, 900)

我想通过创建“targetCost”和“actualCost”的总和并对“行”进行分组来转换这些值,例如

new Foo(1, "P1", 660, 440),
new Foo(2, "P2", 600, 400),
new Foo(3, "P3", 100, 40),
new Foo(4, "P4", 820, 1100)

我现在写的内容:

data.stream()
       .???
       .collect(Collectors.groupingBy(PlannedProjectPOJO::getId));

我怎样才能做到这一点?

最佳答案

使用Collectors.groupingBy是正确的方法,但不应使用单参数版本,该版本将为每个组创建所有项目的列表,您应该使用 the two arg version这需要另一个 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));
    

在您想要聚合多个指定自定义缩减操作的属性时 like suggested in this answer是正确的方法,但是,您可以在分组操作期间执行缩减操作,因此无需将整个数据收集到 Map<…,List> 中。在执行减少之前:

(我假设您现在使用 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提取流元素的属性并创建/返回一个新的关键对象。但这需要一个适当的关键属性持有类(Java 没有通用的 Tuple 类)。

但是还有一个替代方案。通过使用 three-arg form of 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));

关于java - 像在 SQL 中使用 Java lambda 一样对对象进行分组和求和?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43369060/

相关文章:

java - 我可以使用 setStyle() 直接更改节点的子结构类样式吗?

java - map 的 map 根据内部 map 值获取外部 map

java Pair 和 ArrayList 问题

java - 无法用java和Maven运行简单的cucumber程序

java - 我应该迭代一个 Java 集合来获取子集,还是应该先将它转换为数组然后迭代来获取它?

java - 在文件流上应用模式

java - 使用 Hibernate Criteria API 编写 HQL 子句

c++ - 从用类方法编写的 lambda 函数访问类字段

c# - 为什么匿名方法不能赋值给var?

java - 使用 Java 流移除和收集元素