5

Given a list of values to sum up.

List<CartItems> cartItems = ...

BigDecimal totalWeight = cartItems.stream().reduce(BigDecimal.ZERO, (weight, cart)
        -> weight.add(cart.getProduct().getWeight().multiply(BigDecimal.valueOf(cart.getQty()))), BigDecimal::add)
        .setScale(SCALE, RoundingMode.HALF_UP);

Here, cart.getProduct().getWeight() may be null at anytime, since it is an optional field. This will thus throw a java.lang.NullPointerException, if one of the items contains a null value in the weight field of type java.math.BigDecmial.

What is the most concise way to avoid a java.lang.NullPointerException being thrown when an item in a given collection contains a null value other than imposing a nasty conditional check like the following?

BigDecimal totalWeight = products.stream().reduce(BigDecimal.ZERO, (weight, cart)
        -> weight.add(cart.getProduct().getWeight() == null ? BigDecimal.ZERO : cart.getProduct().getWeight().multiply(BigDecimal.valueOf(cart.getQty()))), BigDecimal::add)
        .setScale(SCALE, RoundingMode.HALF_UP);

Similarly, the following will also throw a java.lang.NullPointerException, since the given list contains a null value in it.

List<Integer> list = new ArrayList<Integer>() {{
        add(1);
        add(2);
        add(3);
        add(null);
        add(5);
    }};

Integer sum = list.stream().reduce(0, Integer::sum); // Or
//Integer sum = list.stream().reduce(0, (a, b) -> a + b);

System.out.println("sum : " + sum);
Tiny
  • 27,221
  • 105
  • 339
  • 599
  • What do you want to happen when a null is encountered? Do you just want to filter them out? Like here: http://stackoverflow.com/a/32884225/1743880? – Tunaki Mar 04 '16 at 21:45
  • 2
    you can filter out nulls from the stream with `.filter(Objects::nonNull)` – Misha Mar 04 '16 at 21:48
  • 1
    Define nasty? Is `Optional.ofNullable(...).map(...).orElse(0)` nasty? – Tunaki Mar 04 '16 at 21:59
  • @Tiny adding zero is an empty operation. you could just skip it altogether using filter – njzk2 Mar 04 '16 at 23:10

3 Answers3

3

I think the problem is more with the reduce. You try to do to many things in one function. As I understand it, you want the sum of weight*qty for each cart. I would cut the operation like this:

BigDecimal totalWeight = cartItems.stream()
        .filter(cart -> cart != null
                && cart.getProduct() != null 
                && cart.getProduct().getWeight() != null)
        .map(cart -> cart.getProduct().getWeight().multiply(BigDecimal.valueOf(cart.getQty())))
        .reduce(BigDecimal.ZERO, BigDecimal::add)
        .setScale(SCALE, RoundingMode.HALF_UP);
Pavel
  • 4,912
  • 7
  • 49
  • 69
njzk2
  • 38,969
  • 7
  • 69
  • 107
  • This is not the problem in this case but what if `cart` itself or `cart.getProduct()` returns `null` before `getWeight()` is referenced? – Tiny Mar 05 '16 at 00:09
  • 1
    you can chain more `filter`, or you can test more things in the `filter`: `filter(cart -> cart != null && cart.getProduct() != null && cart.getProduct().getWeight() != null)` – njzk2 Mar 05 '16 at 00:29
1

Here are couple of solutions for this problem.

        List<Integer> list = Arrays.asList(new Integer[]{
                1,
                2,
                3,
                (Integer)null,
                5,
        });

        Integer sum = list.stream().map(i -> i != null ? i : 0).reduce(0, Integer::sum); 

Or

list.replaceAll(s -> s == null ? 0 : s);
Integer sum = list.stream().reduce(0, Integer::sum);
Raghu K Nair
  • 3,854
  • 1
  • 28
  • 45
  • 1
    Yes it can but technically it doesn't answer the question. The OP wants to replace null values with 0. Suppose you have multiplication instead of addition. Then 1 null value and the result is 0, when here, you filter it out. – Tunaki Mar 04 '16 at 22:16
  • Yes thats right. I just saw his comments that it should be considered 0 . my appologies – Raghu K Nair Mar 04 '16 at 22:19
  • 1
    Especially, I dislike this kind of conditional checks `a != null` or `a == null` every time an object is to be referenced. – Tiny Mar 04 '16 at 22:23
  • filtering is the same, as you get an element in your sum that is 0 with your ternary. – njzk2 Mar 04 '16 at 23:03
  • I have replaced the filter with with replace the value based on condition. another way could be calling list.replaceAll(s -> s == null ? 0 : s); before the actual intended operation . – Raghu K Nair Mar 06 '16 at 04:41
1

You can do this:

Integer sum = list.stream().filter(Objects::nonNull).reduce(0, Integer::sum);

Bye!

Luiz Jacó
  • 11
  • 1