I have a List<LedgerEntry> ledgerEntries
and I need to calculate the sums of creditAmount and debitAmount.
class LedgerEntry{
private BigDecimal creditAmount;
private BigDecimal debitAmount;
//getters and setters
}
I have implemented this as,
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
This looks like I'm iterating over the List twice. Is there a way to get this done in one go without having to steam the list twice?
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());
}
You could reduce to a totals entry:
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;
});
Update
In the comments it was correctly pointed out that reduce()
should not modify the initial identifier value, and that collect()
should be used for mutable reductions. Below is a version using collect()
(using the same BiConsumer
as both accumulator and combiner). It also addresses the issue of potential NPEs if the creditAmount
and/or debitAmount
values have not been set.
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);
All of the sudden the pre-Java 8 version is starting to look mighty attractive.
You need to wrap your results into a Pair
of some sort:
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);
}));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With