3

This question is a follow-up to an earlier question: Adding up BigDecimals using Streams

The question related to adding up BigDecimals using Java 8 Streams and Lambda expressions. After implementing the answers given, I ran into another problem: whenever the stream is empty, the Optional::get() method throws a NoSuchElementException.

Consider the following code:

public static void main(String[] args){
    LinkedList<BigDecimal> values = new LinkedList<>();
//        values.add(BigDecimal.valueOf(.1));
//        values.add(BigDecimal.valueOf(1.1));
//        values.add(BigDecimal.valueOf(2.1));
//        values.add(BigDecimal.valueOf(.1));

    // Classical Java approach
    BigDecimal sum = BigDecimal.ZERO;
    for(BigDecimal value : values) {
        System.out.println(value);
        sum = sum.add(value);
    }
    System.out.println("Sum = " + sum);

    // Java 8 approach
    values.forEach((value) -> System.out.println(value));
    System.out.println("Sum = " + values.stream().reduce((x, y) -> x.add(y)).get());
}

The vanilla Java code has no problem with an empty collection, but the new Java 8 code does.

What is the most elegant way to avoid a NSEE here? Certainly we could do:

System.out.println("Sum = " + values == null || values.isEmpty() ? 0 : values.stream().reduce((x, y) -> x.add(y)).get());

But is there a Java-8-ish way to handle empty collections?

Community
  • 1
  • 1
ryvantage
  • 13,064
  • 15
  • 63
  • 112
  • Reverse the order: values.stream().reduce((x, y) -> x.add(y)).ifPresent(s -> System.out.println("sum = " + s)); // only print the sum when it has value. – user_3380739 Dec 03 '16 at 00:49

2 Answers2

6

You should, in this case, not be using the version of reduce that can return an Optional<BigDecimal>.

You should be using the other version, as mentioned before, that provides an identity element in case the stream is empty, that is the whole reason the identity element is there.

So you want to have:

System.out.println("Sum = " + values.stream().reduce(BigDecimal.ZERO, (x, y) -> x.add(y));

Instead of the old version.

In this case you do not care about whether the stream is empty or not, you just want a valid result.

skiwi
  • 66,971
  • 31
  • 131
  • 216
  • Hmm, that's the reason the identity value is there? Does the identity value do anything when the stream is not-null? The myriad options available with `Stream` classes can be confusing. – ryvantage Mar 25 '14 at 18:37
  • @ryvantage The identity value is the equivalent of your `BigDecimal sum = BigDecimal.ZERO;` before your for loop in your Java 7 code. – skiwi Mar 25 '14 at 18:38
  • 2
    Yes, the two-arg `reduce` form (with the identity value) is appropriate here. The identity value is used not only in the case of a zero-element stream, but it is also used as the initial value of the partial results when doing parallel reduction. – Stuart Marks Mar 26 '14 at 00:42
5

While typing the example to ask the question, I found the answer:

Stream::reduce() returns an Optional which has a method: orElse(). So,

System.out.println("Sum = " + values.stream().reduce((x, y) -> x.add(y)).get());

becomes

System.out.println("Sum = " + values.stream().reduce((x, y) -> x.add(y)).orElse(BigDecimal.ZERO));

So I decided to post a Q-and-A.

Lambdas are great. +1 Java.

ryvantage
  • 13,064
  • 15
  • 63
  • 112
  • Did you look at the other answer, which uses the overloaded `reduce()` method, passing `BigDecimal.ZERO` as first argument? That method returns a `BigDecimal` only, rather than `Optional`. – Rohit Jain Mar 25 '14 at 18:19
  • 5
    The mistake is calling get() on an optional for which you don't know the thing is non-empty. Instead, use one of the conditional methods, like orElse() or ifPresent(), or write conditional code based on Optional.isPresent(). – Brian Goetz Apr 27 '14 at 19:48