【问题标题】:Java 8 Sum two object properties in one iterationJava 8 在一次迭代中求和两个对象属性
【发布时间】:2017-07-12 04:33:02
【问题描述】:

我有一个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

这看起来像我在 List 上迭代了两次。有没有一种方法可以一次性完成此任务,而无需两次对列表进行蒸汽处理?

Java 8 之前的版本

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

【问题讨论】:

  • 为什么要使用流?您的“Pre Java 8”版本也是 100% 有效的 Java 8 并且(由于BigDecimals 是不可变的,它实际上并没有做任何事情而被修复)比任何流都更具可读性和可维护性(并且可能更高效)尝试一次计算两个总和的解决方案。
  • @KrazyKalle:谢谢。做了编辑
  • @KrazyKalle。是的。你认为我在括号之间的句子是什么意思(当固定时......不可变时)?
  • 顺便说一下,你可以写.filter(LedgerEntry::getCreditAmount) 来为 lambdas 使用方法引用。

标签: java lambda foreach java-8 java-stream


【解决方案1】:

您可以减少到总计条目:

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

更新

在 cmets 中正确指出 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 之前的版本开始看起来非常有吸引力。

【讨论】:

  • 为了便于阅读,是否可以将 new LedgerEntry() 替换为 LedgerEntry::new
  • @CKing 不。这是初始值,不是方法引用。
  • @CKing 那是因为这个答案显示reduce()的使用无效:第一个参数是identityBinaryOperator不应该修改它.它应该返回另一个对象。需要修改其输入的是集合操作(​​实际上它们以Supplier 作为参数)。
  • @DidierL 请原谅我的无知。你说的对。我会考虑更新我的答案。
  • @Krishan LedgerEntry 有一个构造函数,它接受 creditAmountdebitAmount 的值?
【解决方案2】:

您需要将结果包装成某种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);
                    }));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-08
    • 2023-03-24
    相关资源
    最近更新 更多