按对象数组列表分组而不创建地图

Group by arraylist of objects without creating map

我有对象 PayBill,其中包含项目列表。对象项目包含文章。本文连接到另一个 Nomenclature 对象,该对象存储为 db table,下一个结构为:

Article ProductGroup
------- ------------
010101  Telephone
040444  Computer

我将我的交易列表解析为具有以下属性的对象 Stat:payBillNumberproductGroupamountOfItemssumPriceOfItems。现在 Stat 的列表如下所示:

BillNumber ProductGroup NumberOfItemsInBill SummaryItemsCostInBill
---------- ------------ ------------------- ----------------------
1          Telephone    1                   1000
1          Computer     1                   200
1          Telephone    2                   2000
1          Computer     1                   200
2          Accessories  1                   1500

如何使用以下视图将此 List<Stat> 折叠成新的 List<Stat>

BillNumber ProductGroup NumberOfItemsInBill SummaryItemsCostInBill
---------- ------------ ------------------- ----------------------
    1          Telephone    3                   3000
    1          Computer     2                   400
    2          Accessories  1                   1500

我希望我的数据按 BillNumber 和 ProductGroup 进行分组,金额与价格相加。是否可以不创建新地图?

我想出了一个涉及中间地图的解决方案,但以实用的方式使用 groupingByreducing:

List<Stat> collect = stats.stream()
    .collect(Collectors.groupingBy(Stat::getBillNumber, Collectors.groupingBy(Stat::getProductGroup,
            Collectors.reducing((left, right) -> new Stat(
                    left.getBillNumber(), 
                    left.getProductGroup(), 
                    left.getNumberOfItemsInBill() + right.getNumberOfItemsInBill(),
                    left.getSummaryItemsCostInBill() + right.getSummaryItemsCostInBill()
            )))))
    .values().stream()
    .flatMap(m -> m.values().stream())
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());

请注意,这会导致创建大量 Stat 个对象。如果性能是一个问题,你可以想出一些聚合器并使用 Collectors.of 而不是 Collectors.reducing 或者坚持使用 reducing 并在最后映射它。

您必须创建 Stat 构造函数,但它是这样的:

Collection<Stat> aggregated = stats.stream()
  .collect(Collectors.toMap(
    stat -> new AbstractMap.SimpleEntry<>(stat.getProductGroup(), stat.getBillNumber()),
    stat -> stat,
    (stat1, stat2) -> new Stat(stat1.getBillNumber(), stat1.getProductGroup(), stat1.getNumberOfItemsInBill() + stat2.getNumberOfItemsInBill(), stat1.getSummaryItemsCostInBill() + stat2.getSummaryItemsCostInBill()
  ))
  .values();

我创建了下面的 class 来表示排序形式的数据。

@Data
class Stat{
    int bn;
    String pg;
    int noOfBills;
    int cost;
    public Stat(int bn, String pg, int noOfBills, int cost)
    {
        this.bn = bn;
        this.pg = pg;
        this.noOfBills = noOfBills;
        this.cost = cost;
    }

    @Override
    public String toString()
    {
        return this.bn + " - " + this.pg + " - " + this.noOfBills + " - " + this.cost;
    }
}

这里是我测试的样本数据的主要方法,你可以收集而不是打印。

public static void main(String[] args)
    {
        List<Stat> list = new ArrayList<>();
        list.add(new Stat(1, "Telephone",1, 1000));
        list.add(new Stat(1, "Computer",1, 200));
        list.add(new Stat(1, "Telephone",2, 2000));
        list.add(new Stat(1, "Computer",1, 200));
        list.add(new Stat(2, "Accessories",1, 1500));
        
        list.stream().collect(toMap(Stat::getPg, Function.identity(),
                        (a, b) -> new Stat(a.getBn(), a.getPg(), a.getNoOfBills() + b.getNoOfBills(), a.getCost() + b.getCost())))
                .forEach((id,foo)->System.out.println(foo));
}