在java 8

时间:2018-04-14 15:07:49

标签: java java-stream grouping bigdecimal collectors

我有一个包含其中一个属性的产品列表。并且列表可以包含公共产品名称,其他属性不同。所以我想按产品和使用分组和求和在java 8中共享公用名的产品数量的总和对列表进行分组。

Example:
[  
   {  
      "name":"Product A",
      "amount":"40.00",
      "description":"Info1",
      "number":"65"
   },
   {  
      "name":"Product A",
      "amount":"50.00",
      "description":"Info2",
      "number":"67"
   },
   {  
      "name":"Product A",
      "amount":"100.00",
      "description":"Info3",
      "number":"87"
   },
   {  
      "name":"Product B",
      "amount":"45.00",
      "description":"Info4",
      "number":"86"
   },
   {  
      "name":"Product D",
      "amount":"459.00",
      "description":"Info5",
      "number":"7"
   },
   {  
      "name":"Product B",
      "amount":"50.00",
      "description":"Info6",
      "number":"8"
   }
]

输出应该类似于:

{  
   "Product A = 190.00":[  
      {  
         "name":"Product A",
         "amount":"40.00",
         "description":"Info1",
         "number":"65"
      },
      {  
         "name":"Product A",
         "amount":"50.00",
         "description":"Info2",
         "number":"67"
      },
      {  
         "name":"Product A",
         "amount":"100.00",
         "description":"Info3",
         "number":"87"
      }
   ],
   "Product B=95.00":[  
      {  
         "name":"Product B",
         "amount":"45.00",
         "description":"Info4",
         "number":"86"
      },
      {  
         "name":"Product B",
         "amount":"50.00",
         "description":"Info6",
         "number":"8"
      }
   ],
   "Product D = 459.00":[  
      {  
         "name":"Product D",
         "amount":"459.00",
         "description":"Info5",
         "number":"7"
      }
   ]

我创建了一个bean类ProductBean,其中包含所有字段(名称,数量,描述和编号)以及相同的getter和setter。 而productBeans列出了所有产品。

Map<String, List<ProductBean>> groupByProduct = 
               productBeans.stream()
                     .collect(Collectors.groupingBy(item -> item.getName()))

Map<String, BigDecimal> result = 
      productBeans.stream()
             .collect(Collectors.groupingBy(ProductBean::getName, 
                                Collectors.mapping(ProductBean::getAmount, 
                     Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));

groupByProduct包含按名称分组的产品列表。 结果以产品的关键字和产品的总量作为值。

但是我在这里尝试将产品和总金额映射到产品列表中。我试图结合上面的代码来获得预期的输出,但无法实现。通过遍历地图已经实现了相同的目标。

但是,在合并上述代码以获取产品名称和金额作为键并列为值的地图方面的任何帮助都会有所帮助。

3 个答案:

答案 0 :(得分:3)

您似乎想要Map<String, BigDecimal, List<ProductBean>>这样的内容,其中String表示nameBigDecimal是所有产品数量和{{1}的总和作为该特定组中的产品列表。

这样的地图是不可能的,所以相反,你已经成功地分两步完成了这项工作,效率并不高,并且不会将相关数据保存在一起,而是将它分成两个地图。 / p>

我的建议是创建一个包装类:

List<ProductBean>

这将包含一组class ResultSet { public String getName() { return name; } public void setName(String name) { this.name = name; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount = amount; } public List<ProductBean> getProductBeans() { return productBeans; } public void setProductBeans(List<ProductBean> productBeans) { this.productBeans = productBeans; } private String name; private BigDecimal amount; private List<ProductBean> productBeans; @Override public String toString() { return "ResultSet{" + "name='" + name + '\'' + ", amount=" + amount + ", productBeans=" + productBeans + '}'; } } 维护相关数据,以提高可维护性和可读性。

现在,您可以通过以下方式完成手头的任务:

ProductBean's

List<ResultSet> result = productList.stream() .collect(Collectors.groupingBy(ProductBean::getName)) .entrySet() .stream() .map(e -> { ResultSet resultSet = new ResultSet(); BigDecimal sum = e.getValue().stream() .map(ProductBean::getAmount) .reduce(BigDecimal.ZERO, BigDecimal::add); resultSet.setName(e.getKey()); resultSet.setAmount(sum); resultSet.setProductBeans(e.getValue()); return resultSet; }).collect(Collectors.toList()); 名称分组,然后将每个组映射到ProductBean实例,该实例将封装名称,该特定组中的金额总和以及整个ResultSet在小组中。

您可以通过创建映射方法来进一步重构上述流查询:

ProductBean's

因此,将流查询呈现为:

private static ResultSet mapToResultSet(Map.Entry<String, List<ProductBean>> e) {
        ResultSet resultSet = new ResultSet();
        BigDecimal sum = e.getValue().stream()
                .map(ProductBean::getAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        resultSet.setName(e.getKey());
        resultSet.setAmount(sum);
        resultSet.setProductBeans(e.getValue());
        return resultSet;
}

答案 1 :(得分:0)

嗯,你几乎得到了它。

Collectors.groupingBy允许第二个参数,它是一个将为密钥生成值的收集器。您只需要从第二行传递收集器作为第二个参数:

    Map<String, BigDecimal> result = productBeans
            .stream()
            .collect(
                Collectors.groupingBy(ProductBean::getName,
                Collectors.mapping(ProductBean::getAmount, 
                    Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));

答案 2 :(得分:0)

除了我之前的回答,建议创建一个包装类。如果出于任何原因,我们不想创建一个自定义类 来包装结果,那么另一个解决方案如下:

List<AbstractMap.SimpleEntry<Map.Entry<String, List<ProductBean>>, BigDecimal>> 
         resultSet =
                productBeans.stream()
                            .collect(Collectors.groupingBy(ProductBean::getName))
                            .entrySet()
                            .stream()
                            .map(Main::mapToSimpleEntry)
                            .collect(Collectors.toList());

其中Main是包含mapToSimpleEntry的类,mapToSimpleEntry定义为:

private static AbstractMap.SimpleEntry<Map.Entry<String, List<ProductBean>>, BigDecimal> mapToSimpleEntry(Map.Entry<String, List<ProductBean>> e) {
        BigDecimal sum =
                e.getValue().stream()
                        .map(ProductBean::getAmount)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
        return new AbstractMap.SimpleEntry<>(e, sum);
}

此解决方案的优点是:

  1. 一个人不需要创建自定义类
  2. 少代码