4

How can I accumulate inside a forEach? this is my code:

public Double accumulate() {
        Double accum = 0d;

        numbers.stream().forEach(p-> {

            // if condition
                accum = accum + numbers.getAmount();
            // else
                accum = accum + numbers.getAmountWithInterest();
        });

        return accum;
    }

Maybe I should use map instead of forEach, I tried a couple of things but it didn't work. Thanks

Naman
  • 27,789
  • 26
  • 218
  • 353
Julio
  • 429
  • 2
  • 16
  • 3
    You can't obviously reassign `accum` in the lambda: it's effectively final https://stackoverflow.com/questions/20938095/difference-between-final-and-effectively-final . Additionally, this is a wrong use of `forEach`. Try using `reduce` instead. – terrorrussia-keeps-killing Dec 18 '20 at 23:04
  • Also, a small note, Stream API is not a silver bullet, and you should not use it just because you can do it for a particular case (think first why). `forEach` is the most abused method, and you often can use a regular for-each statement, especially for this case. – terrorrussia-keeps-killing Dec 18 '20 at 23:14

2 Answers2

12

I do not think it is a good idea to make side effect when using lambda. It is a bad way of mixing functional and imperative programming. You can do it easier by

numbers.stream().mapToInt(p-> {
    // if condition
        return numbers.getAmount();
    // else
        return numbers.getAmountWithInterest();
}).sum();
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
HamoriZ
  • 2,370
  • 18
  • 38
-1

You can use mapToDouble and sum instead of forEach with Java Streams:

@Test
public void sumOfDoubleWithStreams()
{
    List<Amount> numbers =
        Arrays.asList(new Amount(10.0, 0.0), new Amount(20.0, 0.05), new Amount(30.0, 0.1));
    double sum = numbers.stream()
        .mapToDouble(
            amount -> amount.hasInterest() ? amount.getAmountWithInterest() : amount.getAmount())
        .sum();
    Assert.assertEquals(64.0d, sum, 0.0d);
}

If you really want to use forEach, you need to have an object that can be mutated inside of the forEach call. You can use AtomicDouble here, or another class like DoubleSummaryStatistics or your own accumulating class.

@Test
public void sumOfDoubleWithForEach()
{
    List<Amount> numbers =
        Arrays.asList(new Amount(10.0, 0.0), new Amount(20.0, 0.05), new Amount(30.0, 0.1));
    AtomicDouble sum = new AtomicDouble();
    numbers.forEach(amount ->
        sum.addAndGet(amount.hasInterest() ? amount.getAmountWithInterest() : amount.getAmount()));
    Assert.assertEquals(64.0d, sum.get(), 0.0d);
}

If you are open to using a third-party library, you can use sumOfDouble on any container from Eclipse Collections.

@Test
public void sumOfDouble()
{
    MutableList<Amount> numbers = 
        Lists.mutable.with(new Amount(10.0, 0.0), new Amount(20.0, 0.05), new Amount(30.0, 0.1));
    double sum = numbers.sumOfDouble(
        amount -> amount.hasInterest() ? amount.getAmountWithInterest() : amount.getAmount());
    Assert.assertEquals(64.0d, sum, 0.0d);
}

I created an Amount class to hold the amount and interest rate:

public static final class Amount
{
    private final double amount;
    private final double interest;

    public Amount(double amount, double interest)
    {
        this.amount = amount;
        this.interest = interest;
    }

    public double getAmountWithInterest()
    {
        return this.amount * (1.0 + this.interest);
    }

    public boolean hasInterest()
    {
        return this.interest > 0.0d;
    }

    public double getAmount()
    {
        return amount;
    }
}

Note: I am a committer for Eclipse Collections.

Donald Raab
  • 6,458
  • 2
  • 36
  • 44