Java 8 在一次迭代中对两个对象属性求和

问题描述 投票:0回答:3

我有一个

List<LedgerEntry> ledgerEntries
,我需要计算creditAmount 和debitAmount 的总和。

class LedgerEntry{
 private BigDecimal creditAmount;
 private BigDecimal debitAmount;

 //getters and setters
}

我已将其实现为,

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

这看起来像是我对列表进行了两次迭代。有没有一种方法可以一次性完成此任务,而不必将列表重复两次?

Java 8 之前的版本

BigDecimal creditTotal = BigDecimal.ZERO;
BigDecimal debitTotal = BigDecimal.ZERO;
for(LedgerEntry entry : ledgerEntries){
  creditTotal = creditTotal.add(entry.getCreditAmount());
  debitTotal = debitTotal.add(entry.getDebitAmount());
}
java lambda foreach java-8 java-stream
3个回答
13
投票

您可以减少到总计条目:

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

更新

在评论中正确指出

reduce()
不应该修改初始标识符值,并且
collect()
应该用于可变约简。下面是使用
collect()
的版本(使用相同的
BiConsumer
作为累加器和组合器)。如果尚未设置
creditAmount
和/或
debitAmount
值,它还解决了潜在 NPE 的问题。

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

突然之间,Java 8 之前的版本开始看起来非常有吸引力。


1
投票

您需要将结果包装成某种形式的

Pair

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

0
投票

如果只有两个值,可以使用Collectors方法

teeing()
(JDK 12+)

来自文档:

返回一个由两个下游收集器组合而成的收集器。传递到结果收集器的每个元素都由两个下游收集器处理,然后使用指定的合并函数将它们的结果合并到最终结果中。

LedgerEntry ledgerEntry = ledgeredEntries.stream()
    .collect(Collectors.teeing(
        Collectors.reducing(BigDecimal.ZERO,
                            p -> p.getCreditAmount(), //you can replace with method reference
                            BigDecimal::add),
        Collectors.reducing(BigDecimal.ZERO,
                            p -> p.getDebitAmount(),
                            BigDecimal::add),
        LedgerEntry::new)
    );
© www.soinside.com 2019 - 2024. All rights reserved.