Java 8 stream - concatenation of collections of objects having the same id

I have a collection of accounts:

class Invoice {
  int month;
  BigDecimal amount
}

      

I would like to combine these invoices, so I get one invoice per month and the amount is the sum of the sum of the invoices for that 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 using java 8 streams?

EDIT: Since my Invoice class was changed and it was not a problem to change them, I chose 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();

      

+8


source to share


5 answers


If you return ok Collection

, it looks 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 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 it Invoice

is mutable ...

+17


source


If you can add the following constructor and merge method to your class 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;
}

      

You can reduce it however you like like 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();

      

I use Collectors.toMap

to do a job that has three arguments: a function that maps stream items to keys, a function that maps stream items to values, and a merge function that is used to merge values ​​when keys collide.

+5


source


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

      

0


source


Here is my library solution: 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();

      

0


source


I think if your app does not support lambda then this might be a suitable answer like (Android minSdkVersion = 16 does 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;
}

      

0


source







All Articles