Java 8 stream - merge collections of objects sharing the same Id

回眸只為那壹抹淺笑 提交于 2020-01-13 08:09:12

问题


I have a collection of invoices :

class Invoice {
  int month;
  BigDecimal amount
}

I'd like to merge these invoices, so I get one invoice per month, and the amount is the sum of the invoices amount for this month.

For example:

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

Output:

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

How can I do this with java 8 streams?

EDIT : as my Invoice class was mutable and it was not a problem to modify them, I choosed Eugene's solution

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

回答1:


If you are OK returning a Collection it would look like this:

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

If you really need a 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())));

Both obviously assume that Invoice is mutable...




回答2:


If you could add the following copy constructor and merge method to your Invoice class:

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

You could reduce as you want, as follows:

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

I'm using Collectors.toMap to do the job, which has three arguments: a function that maps elements of the stream to keys, a function that maps elements of the stream to values and a merge function that is used to combine values when there are collisions on the keys.




回答3:


You can do something like

    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:


Here is the solution by My library: 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:


I think if your application do not support lambda than this might be a suitable answer eg (Android minSdkVersion=16 do not support 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;
}


来源:https://stackoverflow.com/questions/44540040/java-8-stream-merge-collections-of-objects-sharing-the-same-id

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!