使用 Java lambda 对 SQL 中的对象进行分组和求和? [英] Group by and sum objects like in SQL with Java lambdas?

查看:22
本文介绍了使用 Java lambda 对 SQL 中的对象进行分组和求和?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有这些字段的类 Foo:

I have a class Foo with these fields:

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

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

我得到了一个此类对象的数组列表.例如:

I get an arraylist of objects of this class. e.g.:

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"的总和并对行"进行分组来转换这些值,例如

I want to transform these values by creating a sum of "targetCost" and "actualCost" and grouping the "row" e.g.

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 是正确的方法,但不是使用单个参数版本,后者将为每个项目创建一个列表组你应该使用 两个 arg 版本,它采用另一个 Collector 来决定如何聚合每个组的元素.

Using Collectors.groupingBy is the right approach but instead of using the single argument version which will create a list of all items for each group you should use the two arg version which takes another Collector which determines how to aggregate the elements of each group.

当您想要聚合元素的单个属性或仅计算每组元素的数量时,这尤其顺畅:

This is especially smooth when you want to aggregate a single property of the elements or just count the number of elements per group:

  • 计数:

list.stream()
  .collect(Collectors.groupingBy(foo -> foo.id, Collectors.counting()))
  .forEach((id,count)->System.out.println(id+"	"+count));

  • 总结一个属性:

  • Summing up one property:

    list.stream()
      .collect(Collectors.groupingBy(foo -> foo.id,
                                        Collectors.summingInt(foo->foo.targetCost)))
      .forEach((id,sumTargetCost)->System.out.println(id+"	"+sumTargetCost));
    

  • 如果您想聚合多个指定自定义归约操作的属性就像本答案中建议的那样然而,正确的做法是,您可以在分组操作期间执行归约,因此无需在执行归约之前将整个数据收集到 Map<...,List> 中:

    In your case when you want to aggregate more than one property specifying a custom reduction operation like suggested in this answer is the right approach, however, you can perform the reduction right during the grouping operation so there is no need to collect the entire data into a Map<…,List> before performing the reduction:

    (我假设您现在使用 import static java.util.stream.Collectors.*;...)

    (I assume you use a 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 没有通用的元组类).

    The first thing which jumps into the programmers mind, is to use groupingBy to extract the properties of the stream’s elements and create/return a new key object. But this requires an appropriate holder class for the key properties (and Java has no general purpose Tuple class).

    但是还有一个选择.通过使用 三参数形式的groupingBy我们可以为实际的Map指定一个供应商代码> 实现将确定密钥相等性.通过使用带有比较多个属性的比较器的排序映射,我们无需额外的类即可获得所需的行为.我们只需要注意不要使用我们的比较器忽略的键实例中的属性,因为它们将具有任​​意值:

    But there is an alternative. By using the three-arg form of groupingBy we can specify a supplier for the actual Map implementation which will determine the key equality. By using a sorted map with a comparator comparing multiple properties we get the desired behavior without the need for an additional class. We only have to take care not to use properties from the key instances our comparator ignored, as they will have just arbitrary values:

    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+"	"+group.actualCost+"	"+targetCostSum));
    

    这篇关于使用 Java lambda 对 SQL 中的对象进行分组和求和?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆