2017-02-22 3 views
7

Ich habe eine List<LedgerEntry> ledgerEntries und ich muss die Summen von creditAmount und debitAmount berechnen.Java 8 Summe zwei Objekteigenschaften in einer Iteration

class LedgerEntry{ 
private BigDecimal creditAmount; 
private BigDecimal debitAmount; 

//getters and setters 
} 

Ich habe implementiert dies als,

BigDecimal creditTotal = ledgeredEntries.stream().map(p ->p.getCreditAmount()). 
reduce(BigDecimal.ZERO, BigDecimal::add); 
BigDecimal debitTotal = ledgeredEntries.stream().map(p ->p.getDebitAmount()). 
reduce(BigDecimal.ZERO, BigDecimal::add); 

//... 
//Use creditTotal, debitTotal later 

Das sieht aus wie ich über die Liste zweimal bin laufen. Gibt es eine Möglichkeit, dies auf einmal zu erledigen, ohne die Liste zweimal durchspielen zu müssen?

Pre Java 8 Version

BigDecimal creditTotal = BigDecimal.ZERO; 
BigDecimal debitTotal = BigDecimal.ZERO; 
for(LedgerEntry entry : ledgerEntries){ 
    creditTotal = creditTotal.add(entry.getCreditAmount()); 
    debitTotal = debitTotal.add(entry.getDebitAmount()); 
} 
+3

Warum Sie Ströme verwenden wollen? Ihre "Pre Java 8" -Version ist auch 100% valides Java 8 und (wenn es für die Tatsache, dass es nichts wirklich tut, weil BigDecimal unveränderlich ist) besser lesbar und wartbar (und wahrscheinlich performanter) als jeder Stream ist Lösung, die versucht, die beiden Summen auf einmal zu berechnen. – Hoopje

+0

@KrazyKalle: Danke. tat die Bearbeitung – Krishan

+0

@KrazyKalle. Ja. Was meinst du mit dem Satz zwischen den Klammern (wenn fest ... unveränderlich)? – Hoopje

Antwort

12

Sie zu einem Summeneintrag reduzieren könnte:

LedgerEntry totalsEntry = entries.stream().reduce(new LedgerEntry(), (te, e) -> { 
    te.setCreditAmount(te.getCreditAmount().add(e.getCreditAmount())); 
    te.setDebitAmount(te.getDebitAmount().add(e.getDebitAmount())); 

    return te; 
}); 

aktualisieren

In den Kommentaren es zu Recht ausgeführt wurde, dass reduce() sollte den ursprünglichen Bezeichner nicht ändern Wert, und das collect() sollte für veränderliche Reduktionen verwendet werden. Im Folgenden finden Sie eine Version mit collect() (mit demselben BiConsumer als Akkumulator und Combiner). Es behandelt auch das Problem potenzieller NPEs, wenn die Werte creditAmount und/oder debitAmount nicht festgelegt wurden.

BiConsumer<LedgerEntry, LedgerEntry> ac = (e1, e2) -> { 
    BigDecimal creditAmount = e1.getCreditAmount() != null ? e1.getCreditAmount() : BigDecimal.ZERO; 
    BigDecimal debitAmount = e1.getDebitAmount() != null ? e1.getDebitAmount() : BigDecimal.ZERO; 

    e1.setCreditAmount(creditAmount.add(e2.getCreditAmount())); 
    e1.setDebitAmount(debitAmount.add(e2.getDebitAmount())); 
}; 

LedgerEntry totalsEntry = entries.stream().collect(LedgerEntry::new, ac, ac); 

Plötzlich sieht die Pre-Java 8-Version mächtig attraktiv aus.

+1

Kann 'new LedgerEntry()' durch 'LedgerEntry :: new' aus Gründen der Lesbarkeit ersetzt werden? – CKing

+3

@CKing Nein. Es ist der Anfangswert, keine Referenz zur Methode. –

+0

Bekannt. Ich nahm an, "reduce" würde eine überladene Form haben, die eine funktionale Schnittstelle wie einen "Supplier" braucht, aber rate, dass es so etwas nicht gibt. – CKing

1

Sie benötigen Ergebnisse in eine Pair irgendeiner Art zu verpacken:

stream 
     .parallel() 
     .reduce(new AbstractMap.SimpleEntry<>(BigDecimal.ZERO, BigDecimal.ZERO), 
        (entry, ledger) -> { 
         BigDecimal credit = BigDecimal.ZERO.add(entry.getKey()).add(ledger.getCreditAmount()); 
         BigDecimal debit = BigDecimal.ZERO.add(entry.getValue()).add(ledger.getDebitAmount()); 
         return new AbstractMap.SimpleEntry<>(credit, debit); 
        }, (left, right) -> { 
         BigDecimal credit = BigDecimal.ZERO.add(left.getKey()).add(right.getKey()); 
         BigDecimal debit = BigDecimal.ZERO.add(left.getValue()).add(right.getValue()); 
         return new AbstractMap.SimpleEntry<>(credit, debit); 
        }));