2017-06-14 1 views
3

ich eine Sammlung von Rechnungen haben:Java 8 Stream - Sammlungen von Objekten verschmelzen die gleiche Id teilen

class Invoice { 
    int month; 
    BigDecimal amount 
} 

Ich möchte diese Rechnungen fusionieren, so dass ich eine Rechnung pro Monat, und die Menge die Summe der Rechnungsbeträge für diesen Monat.

Zum Beispiel:

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

Ausgang:

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

Wie kann ich das mit Java-8-Streams?

EDIT: als meine Rechnungsklasse wandelbar und es war kein Problem, sie zu ändern, habe ich mich für Eugens Lösung

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

Antwort

8

Wenn Sie auf OK sind eine Collection Rückkehr es würde wie folgt aussehen:

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

Wenn Sie wirklich eine List brauchen:

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

Beide offenbar davon aus, dass Invoice wandelbar ist ...

+0

oder verwenden Sie 'Collectors.collectingAndThen (Collectors.toMap (...), m-> neue ArrayList <> (m.values ​​()))' ... – Holger

+0

@Holger guter Punkt! bearbeitet ... – Eugene

+2

Hallo! 2 Kommentare ... 1) Vielleicht sollte es "list.stream(). Sammeln (...)" und 2) Sie mutieren die ursprünglichen 'Rechnung' Elemente, das sieht für mich nicht korrekt ... Sie könnte es lösen, indem Sie einen Kopierkonstruktor hinzufügen und im Wert-Mapper verwenden –

0

Sie können wie etwas tun

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

Hier ist die Lösung von Meine Bibliothek ist: 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(); 
3

Wenn Sie das könnte hinzufügen folgende Kopierkonstruktor und Merge-Methode zu Ihrem Invoice Klasse:

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

Sie könnten reduzieren, wie Sie wollen, wie folgt:

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

ich Collectors.toMap bin mit der Arbeit zu tun, die drei Argumente hat: eine Funktion, die auf Tasten Elemente des Stroms abbildet, eine Funktion, die Karten Elemente des Stream zu Werten und eine Zusammenführungsfunktion, die zum Kombinieren von Werten verwendet wird, wenn Kollisionen auf den Schlüsseln auftreten.