【问题标题】:Java 8 stream - merge collections of objects sharing the same IdJava 8 流 - 合并共享相同 ID 的对象集合
【发布时间】:2020-04-11 03:37:06
【问题描述】:

我有一组发票:

class Invoice {
  int month;
  BigDecimal amount
}

我想合并这些发票,所以我每月收到一张发票​​,金额是本月发票金额的总和。

例如:

invoice 1 : {month:1,amount:1000}
invoice 2 : {month:1,amount:300}
invoice 3 : {month:2,amount:2000}

输出:

invoice 1 : {month:1,amount:1300}
invoice 2 : {month:2,amount:2000}

如何使用 java 8 流做到这一点?

编辑:由于我的 Invoice 类是可变的,修改它们不是问题,我选择了 Eugene 的解决方案

Collection<Invoice>  invoices = list.collect(Collectors.toMap(Invoice::getMonth, Function.identity(), (left, right) -> {
                left.setAmount(left.getAmount().add(right.getAmount()));
                return left;
            })).values();

【问题讨论】:

    标签: java java-stream


    【解决方案1】:

    如果您可以返回 Collection,它将如下所示:

    Collection<Invoice>  invoices = list.collect(Collectors.toMap(Invoice::getMonth, Function.identity(), (left, right) -> {
                    left.setAmount(left.getAmount().add(right.getAmount()));
                    return left;
                })).values();
    

    如果你真的需要List

     list.stream().collect(Collectors.collectingAndThen(Collectors.toMap(Invoice::getMonth, Function.identity(), (left, right) -> {
                    left.setAmount(left.getAmount().add(right.getAmount()));
                    return left;
                }), m -> new ArrayList<>(m.values())));
    

    显然两者都假设Invoice 是可变的......

    【讨论】:

    • 或使用Collectors.collectingAndThen(Collectors.toMap(…), m-&gt;new ArrayList&lt;&gt;(m.values()))...
    • 嗨! 2 cmets... 1)也许应该是 list.stream().collect(...) 和 2)您正在改变原始的 Invoice 元素,这对我来说看起来不正确...您可以通过添加复制构造函数并使用它来解决它在值映射器中
    【解决方案2】:

    如果您可以将以下复制构造函数和合并方法添加到您的 Invoice 类中:

    public Invoice(Invoice another) {
        this.month = another.month;
        this.amount = another.amount;
    }
    
    public Invoice merge(Invoice another) {
        amount = amount.add(another.amount); // BigDecimal is immutable
        return this;
    }
    

    你可以随意减少,如下:

    Collection<Invoice> result = list.stream()
        .collect(Collectors.toMap(
            Invoice::getMonth, // use month as key
            Invoice::new,      // use copy constructor => don't mutate original invoices
            Invoice::merge))   // merge invoices with same month
        .values();
    

    我正在使用Collectors.toMap 来完成这项工作,它具有三个参数:一个将流元素映射到键的函数、一个将流元素映射到值的函数以及一个用于合并的合并函数键发生冲突时的值。

    【讨论】:

    • 如果你需要一个(不可修改的)List而不是collection,你可以使用List.copyOf(result)
    【解决方案3】:
    Collection<Invoice> result = invoices.stream().collect(groupingBy(i -> i.month,
                    collectingAndThen(
                        reducing((Invoice i1, Invoice i2) -> new Invoice(i1.month, i1.amount + i2.amount)),
                            Optional::get))).values();
    

    【讨论】:

      【解决方案4】:

      你可以这样做

          Map<Integer, Invoice> invoiceMap = invoices.stream()
                  .collect(Collectors.groupingBy(                   // group invoices by month
                          invoice -> invoice.month
                  ))
                  .entrySet().stream()                              // once you have them grouped stream then again so...
                  .collect(Collectors.toMap(
                          entry -> entry.getKey(),                  // we can mantain the key (month)
                          entry -> entry.getValue().stream()        // and streaming all month's invoices
                              .reduce((invoice, invoice2) ->        // add all the ammounts
                                      new Invoice(invoice.month, invoice.amount.add(invoice2.amount)))
                                  .orElse(new Invoice(entry.getKey(), new BigDecimal(0)))          // In case we don't have any invoice (unlikeable)
                  ));
      

      【讨论】:

        【解决方案5】:

        这是我的图书馆的解决方案:AbacusUtil

        Stream.of(invoices)
              .groupBy2(Invoice::getMonth, Invoice::getAmount, BigDecimal::add)  
              .map(e -> new Invoice(e.getKey(), e.getValue())) // Probably we should not modify original invoices. create new instances.
              .toList();
        

        【讨论】:

          【解决方案6】:

          我认为如果您的应用程序不支持 lambda,这可能是一个合适的答案,例如(Android minSdkVersion=16 不支持 lambda)

          public static List<Invoice> mergeAmount(List<Invoice> invoiceList) {
           List<Invoice> newInvoiceList = new ArrayList<>();
            for(Invoice inv: invoiceList) {
              boolean isThere = false;
               for (Invoice inv1: newInvoiceList) {
                if (inv1.getAmount() == inv.getAmount()) {
                   inv1.setAmount(inv1.getAmoount()+inv.getAmount());
                   isThere = true;
                   break;
                 }             
               }
              if (!isThere) {
                  newInvoiceList.add(inv);
              } 
           }
            return newInvoiceList;
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-10-24
            • 2015-10-20
            • 2019-08-02
            • 1970-01-01
            • 2017-08-08
            • 1970-01-01
            • 2021-01-08
            • 1970-01-01
            相关资源
            最近更新 更多