【问题标题】:How avoid duplicates in result from stream? [duplicate]如何避免流的结果重复? [复制]
【发布时间】:2021-06-13 14:09:03
【问题描述】:

CashBox 类:

public class CashBox {
    private long cashBoxId;
    private BigDecimal totalAmount;
    private long merchantId;

    // all-args constructor
}

商家类:

public class Merchant {
    private long merchantId;
    private BigDecimal totalAmount;

   // all-args constructor
}

输入数据:

List<CashBox> cashBoxes = List.of(
    new CashBox(1, new BigDecimal(1000), 1),
    new CashBox(2, new BigDecimal(2000), 1),
    new CashBox(3, new BigDecimal(3000), 2),
    new CashBox(4, new BigDecimal(500), 2)); 

我的任务

计算每个商户的总金额并返回商户列表

我正在尝试使用 Stream API 解决此任务。并写了如下代码:

List<Merchant> merchant =  cashBoxes.stream()
    .map(merch -> new Merchant(
        merch.getMerchantId(), 
        cashBoxes.stream()
                 .filter(cashBox -> cashBox.getMerchantId() == merch.getMerchantId())
                 .map(CashBox::getTotalAmount)
                 .reduce(BigDecimal.ZERO, BigDecimal::add)))
    .collect(Collectors.toList());

结果

[Merchant{merchantId=1, totalAmount=3000}, Merchant{merchantId=1, totalAmount=3000}, Merchant{merchantId=2, totalAmount=3500}, Merchant{merchantId=2, totalAmount=3500}]

但显然,流返回四个对象而不是所需的两个。 我意识到,地图(第 2 行)为每个 cashBoxId 创建了四个对象。而且我不知道如何按merchantId 过滤或获得不重复的结果。

【问题讨论】:

    标签: java java-stream grouping


    【解决方案1】:

    一种方法是使用groupingBy。按商家 ID 分组,然后对于每个组,映射到总金额,并使用相应的收集器进行缩减。这将为您提供包含商家 ID 和总金额的 Map&lt;Long, BigDecimal&gt;。然后,您可以将此地图的每个条目映射到商家:

    cashBoxes.stream().collect(Collectors.groupingBy(
        CashBox::getMerchantId, // group by merchant Id
        // for each group...
        Collectors.mapping(// map to total amount
            CashBox::getTotalAmount,
            Collectors.reducing(BigDecimal.ZERO, BigDecimal::add) // sum
        )
    )).entrySet().stream()
        .map(x -> new Merchant(x.getKey(), x.getValue())) // map to merchant
        .collect(Collectors.toList());
    

    【讨论】:

      【解决方案2】:

      这是使用单个 Stream 的“单线”。这个想法是利用使用Stream::toMap进行分组的优势,使用Function&lt;CashBox, Merchant&gt;映射值并使用BinaryOperator&lt;Merchant&gt;将它们合并为一个对象:

      Collection<Merchant> merchants = cashBoxes.stream()
          .collect(Collectors.toMap(
              CashBox::getMerchantId,
              cashBox -> new Merchant(cashBox.getMerchantId(), cashBox.getTotalAmount()),
              (l, r) -> {
                  l.setTotalAmount(l.getTotalAmount().add(r.getTotalAmount()));
                  return l;
              }
          ))
          .values();
      

      【讨论】:

      • 1.为什么你需要if..else,你不是已经减少了所有具有相同merchantId 的钱箱列表吗? 2.toMap would have simplified的使用
      • @Naman:我错过了!感谢您的来信!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-03-30
      • 2012-06-29
      • 2023-03-22
      • 2019-08-20
      • 2018-08-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多