在Java 8中,如何通过分组和求和过滤对象列表?

时间:2019-06-18 13:20:53

标签: java lambda java-8

假设我有一个对象列表:

objList
Integer id Double price String userCode
         1         10.5             AAA
         2         9.5              AAA
         3         10.0             AAA
         4         10.0             BBB
         5         10.0             BBB

我想修改列表,以便我可以按userCode分组并汇总价格,如下所示:

 objList
    Integer id Double price String userCode
             1         30.0             AAA
             4         20.0             BBB

我如何在Java 8中使用lambda实现此目的?如果可能的话,我希望在不创建新列表且不丢失其他对象字段(例如本例中的id)的情况下实现这一目标。我已经看到了其他答案,但是所有其他答案都需要创建新列表或集合。

我已经尝试过:

Collection<Obj1> objListSorted = objList.stream().collect(
                    toMap(
                            f -> f.getId(),
                            Function.identity(),
                            (s, a) -> new Obj1(
                                        (int) s.getId(), 
                                        s.getPrice() + a.getPrice(),
                                        s.getUserCode()
                                        ))
                          )
                    .values();      

但是实际上,我正在使用具有80多个字段的模型类,因此我想通过不创建Obj1的新实例来实现这一点。我确实想保留组中的第一个ID,以及保留对象的所有其他字段。另外,如果可能的话,我想将最终输出作为列表而不是集合。

2 个答案:

答案 0 :(得分:1)

您可以将modelBuilder.Entity<Tag>().ToTable("Tag");与下游groupingBy一起使用。您可以使用Collector来添加价格,但是会返回您不想要的reducing,但是可以保证它存在,因此您可以Optional得出结果。

get()

由于创建的值中没有排序,因此要求将其变为Collection<Obj1> added = objList.stream() .collect(groupingBy(Obj1::getUserCode), collectingAndThen( reducing((o1, o2) -> { // not creating another instance, but: side effect! o1.setPrice(o1.getPrice()+o2.getPrice()); return o1; }), opt -> opt.get() )) .values(); 几乎没有意义,因此添加的任何内容都是任意的。你当然可以做

List

,然后输入您的List<Obj1> asList = new ArrayList<>(added); 。如果您想对结果进行排序,则可以使用List和适用的groupingBy来进行TreeMap,这样可以保证Comparator的结果也可以按此排序(不过,您仍然需要将其设置为values())。

答案 1 :(得分:1)

尝试一下

解决方案1 ​​

一种实现方法是

List<Pricing> allPricings = getAllPricings();

List<Pricing> result = allPricings.stream()
        .collect(Collectors.groupingBy(Pricing::getUserCode))
        .entrySet().stream()
        .map(e -> e.getValue().stream()
                .reduce((f1,f2) -> new Pricing(f1.getId(),f1.getPrice() + f2.getPrice(),f1.getUserCode())))
        .map(f -> f.get())
        .collect(Collectors.toList());

解决方案2

这是一种更紧凑的方式

Collection<Pricing> result2 = 
        allPricings.stream()
                .collect(groupingBy(Pricing::getUserCode, 
                        collectingAndThen(
                                reducing((p1, p2)-> new Pricing(p1.getId(),p1.getPrice() + p2.getPrice(),p2.getUserCode())), Optional::get)))
                .values();

解决方案3

如果您不想创建该对象,请尝试以下对象。但是此实现的副作用是它会更改原始列表的内容,因此最好不要使用原始列表,而要使用克隆的列表。

Collection<Pricing> result3 =
        allPricings.stream()
                .collect(groupingBy(Pricing::getUserCode,
                        collectingAndThen(
                                reducing((p1, p2)-> {
                                    p1.setPrice(p1.getPrice() + p2.getPrice());
                                    return p1;
                                }), Optional::get)))
                .values();

getAllPricings的示例功能

private static List<Pricing> getAllPricings() {
    List<Pricing> pricings = new ArrayList<>();
    pricings.add(new Pricing(1, 10.5, "AAA"));
    pricings.add(new Pricing(2, 9.5, "AAA"));
    pricings.add(new Pricing(3, 10.0, "AAA"));
    pricings.add(new Pricing(4, 10.0, "BBB"));
    pricings.add(new Pricing(5, 10.0, "BBB"));
    return pricings;
}

结果

[
  {
    "id": 1,
    "price": 30,
    "userCode": "AAA"
  },
  {
    "id": 4,
    "price": 20,
    "userCode": "BBB"
  }
]