在 java 8 中并行使用 BigDecimal 进行分组和求和

Grouping and summing using BigDecimal parallelly in java 8

我有一个产品列表,其中包含一个属性。并且该列表可以包含通用的产品名称,而其他属性不同。所以我想使用分组和求和按产品和在 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 class 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 具有按名称分组的产品列表。 结果给出产品的地图作为键和该产品的总量作为值。

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

但是任何有助于合并上述代码以获取以产品名称和数量作为键并以列表作为值的地图的帮助都会有所帮助。

嗯,你几乎明白了。

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

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

您似乎想要 Map<String, BigDecimal, List<ProductBean>> 之类的东西,其中字符串代表 nameBigDecimal 是所有产品数量的总和,List<ProductBean>是该特定组中的产品列表。

这样的地图是不可能的,因此,您设法分两步完成,这不是很有效,并且它不会将相关数据放在一起,而是将它们分成两个地图。

我的建议是这样创建一个包装器 class:

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;
}

因此,将流查询呈现为:

List<ResultSet> result = productList.stream()
                .collect(Collectors.groupingBy(ProductBean::getName))
                .entrySet()
                .stream()
                .map(Main::mapToResultSet) // Main representing the class containing the mapToResultSet method
                .collect(Collectors.toList());

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

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 的 class,而 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. 不需要创建自定义 class
  2. 更少的代码