2017-02-22 38 views
7

Mam List<LedgerEntry> ledgerEntries i potrzebuję obliczyć sumy wartości creditAmount i debitAmount.Java 8 Sumowanie dwóch właściwości obiektu w jednej iteracji

class LedgerEntry{ 
private BigDecimal creditAmount; 
private BigDecimal debitAmount; 

//getters and setters 
} 

I wprowadziły to jak

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 

To wygląda jakbym iteracji po liście dwukrotnie. Czy istnieje sposób na zrobienie tego za jednym zamachem bez konieczności parowania listy dwukrotnie?

Pre Java 8 wersja

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

Dlaczego chcesz używać strumieni? Twoja wersja "Pre Java 8" jest również w 100% poprawną Javą 8 i (gdy jest ustalona, ​​ponieważ nie robi nic, ponieważ 'BigDecimal's są niezmienne) bardziej czytelna i możliwa do utrzymania (i prawdopodobnie bardziej wydajna) niż jakikolwiek strumień rozwiązanie, które próbuje obliczyć dwie sumy naraz. – Hoopje

+0

@KrazyKalle: Dzięki. zrobił edycję – Krishan

+0

@KrazyKalle. Tak. Jak myślisz, co miałem na myśli, mówiąc o zdaniu między nawiasami (gdy jest stały ... niezmienny)? – Hoopje

Odpowiedz

12

Można zredukować do wpisu Łącznie:

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

Aktualizacji

W komentarzach zostało prawidłowo wskazał, że reduce() nie powinien modyfikować początkowego identyfikatora wartość, i że collect() należy użyć do modyfikowania redukcji. Poniżej znajduje się wersja używająca collect() (używając tego samego BiConsumer jako akumulatora i sumatora). Dotyczy również kwestii potencjalnych NPE, jeśli wartości creditAmount i/lub debitAmount nie zostały ustawione.

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

Nagle wersja pre-Java 8 zaczyna wyglądać na bardzo atrakcyjną.

+1

Czy "nowy LedgerEntry()" można zastąpić 'LedgerEntry :: new' ze względu na czytelność? – CKing

+3

@CKing No. To jest wartość początkowa, a nie referencja do metody. –

+0

Noted. Zakładam, że 'reduce' będzie miał przeciążoną postać, która ma funkcjonalny interfejs, taki jak' dostawca', ale zgadnij, że nie ma czegoś takiego. – CKing

1

Trzeba owinąć swoje wyniki w Pair jakiejś:

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