Java 8流 - 合并共享相同Id的对象集合

时间:2017-06-14 08:55:47

标签: java java-stream

我有一系列发票:

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流做到这一点?

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

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

6 个答案:

答案 0 :(得分:17)

如果你可以返回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是可变的......

答案 1 :(得分:6)

如果您可以将以下复制构造函数和合并方法添加到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来完成这项工作,它有三个参数:一个将流的元素映射到键的函数,一个将流的元素映射到值的函数和一个合并函数用于在键上发生碰撞时组合值。

答案 2 :(得分:1)

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();

答案 3 :(得分:0)

您可以执行类似

的操作
    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)
            ));

答案 4 :(得分:0)

以下是我的图书馆的解决方案: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();

答案 5 :(得分:0)

我认为,如果您的应用程序不支持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;
}