不适用于循环的流

问题描述:

BigDecimal getInterest(List<Investment> investments) { 
     BigDecimal interest = BigDecimal.ZERO; 
     for (Investment i: investments) { 
      i.getTransactions().stream() 
        .map(Transaction::getAmount) 
        .forEach(interest::add); 
     } 
     return interest; 
    } 

此方法的问题是它始终返回零。它看起来像.forEach()没有消耗它的论点。但是,如果我按照下面的方式编写它,一切工作正常。任何人都知道为什么第一种方法不起作用?不适用于循环的流

BigDecimal getInterest(List<Investment> investments) { 
     BigDecimal interest = BigDecimal.ZERO; 
     for (Investment i: investments) { 
      interestPaid = interest.add(i.getTransactions().stream() 
        .map(Transaction::getAmount) 
        .reduce(BigDecimal.ZERO, BigDecimal::add)); 
     } 
     return interest; 
    } 
+4

也许是因为BigDecimal是不可变的,因此它在调用add时而不是更新原始调用时返回新实例? – Koekje

+1

@Eran:这是'BigDecimal',这是这里的问题。 – f*

+1

@f*我错过了,谢谢。其实它是BigDecimal,但同样的问题。 – Eran

BigDecimal是不变的,所以你forEach呼唤add,但与结果做任何事情。在这种情况下,reduce是正确的流操作符。

如果看到Adding up BigDecimals using Streams。您应该使用.reduce(BigDecimal.ZERO, BigDecimal::add),这将使你的循环体:

interest = i.getTransactions().stream() 
       .map(Transaction::getAmount) 
       .reduce(BigDecimal.ZERO, BigDecimal::add); 

由于BigDecimal是不可改变的,呼吁add因为它不会改变的价值。它会返回一个新的BigDecimalforEach只是忽略返回的任何值。

interest将始终保持其初始值BigDecimal.ZERO

相比之下,reduce结合使用给定BinaryOperator的元素。 BigDecimal::add实际上是(a, b) -> a.add(b)的简写形式,此运算符将应用于合并所有流元素。

由于accepted answer解释,BigDecimal.add不会修改该实例,但会返回一个新值并且您的第一个变体未使用结果。作为一个附录,如果你不在第二个变体中使用reduce的结果,也会发生同样的情况,但是在那里,你不仅仅将结果传递给前一个值的调用add,还需要分配结果随后add到你的本地变量。

但值得注意的是,你的混合循环和流操作不一致。您可以将整个操作表示为单个Stream操作:

BigDecimal getInterest(List<Investment> investments) { 
    return investments.stream() 
     .flatMap(i -> i.getTransactions().stream()) 
     .map(Transaction::getAmount) 
     .reduce(BigDecimal.ZERO, BigDecimal::add); 
}