NPE downstream java reduces performance

Recently, while working with Java 8 threads, I encountered a NullPointerException during a shrink operation while working with the following test cases:

 private static final BinaryOperator<Integer> sum = (a, b) -> { if (a == null) return b; if (b == null) return a; return Integer.sum(a, b); }; List<Integer> s = new ArrayList<>(); s.add(null); s.add(null); s.add(null); Integer i = s.stream().reduce(sum).orElse(null); // throws NPE Integer i = s.stream().reduce(sum).orElse(2); // throws NPE Integer i = s.stream().reduce(null,(a, b)->null); // returns a value ie null 

Or alternatively:

 Integer i = s.stream().filter(Objects::nonNull).reduce(Integer::sum).orElse(null); // returns a value ie null 

After checking the reduction operation, I came across this class that performs the reduction operation:

 class ReducingSink implements AccumulatingSink<T, Optional<T>, ReducingSink> { private boolean empty; private T state; public void begin(long size) { empty = true; state = null; } @Override public void accept(T t) { if (empty) { empty = false; state = t; } else { state = operator.apply(state, t); } } @Override public Optional<T> get() { return empty ? Optional.empty() : Optional.of(state); } @Override public void combine(ReducingSink other) { if (!other.empty) accept(other.state); } } 

In the above code, you will see that the get() method returns an optional value if boolean empty is false, and in my case false, and state is null, so Optional.of(null) throws a NullPointerException . In my case, I have a binary operator that allows null .

So, I think the code

 return empty ? Optional.empty() : Optional.of(state); 

should be changed to

 return empty || state == null ? Optional.empty() : Optional.of(state); 

Like my binary operator (which has the task of decreasing), and this is normal with null .

+5
source share
2 answers

documentation of the reduction operation in which you use the states:

Throws: NullPointerException - if the result of the reduction is null

So the NPE you see is documented and intended for the result, even if your binary operator is in order with zero.

The documentation is even more detailed, giving additional information with some equivalent code:

  boolean foundAny = false; T result = null; for (T element : this stream) { if (!foundAny) { foundAny = true; result = element; } else result = accumulator.apply(result, element); } return foundAny ? Optional.of(result) : Optional.empty(); 

In this case, the NPE is thrown on the last line.

If your proposed change is applied in the library, we will not be able to distinguish the result of reducing the empty stream from the stream in which the result of the reduction is zero.

+7
source

I can’t say why you should work with zeros, for a start this seems like a bad idea. And, as you saw, you cannot reduce use zero as input. You can create your own Collector (you cannot create your own Reducer ).

What do you have:

  Double result = s.stream() .filter(Objects::nonNull) .reduce(Double::sum) .orElse(null); 

excellent excellent . The only way to get a null result is when all the elements from your input are zero, so filtering them at the initial stage is the way to go. For fun, I decided to write a custom collector (I can’t say why, I thought it would be interesting, I think)

  Double result = s.stream() .parallel() .collect( () -> new Double[] { null }, (left, right) -> { if (right != null) { if (left[0] != null) { left[0] = right + left[0]; } else { left[0] = right; } } }, (left, right) -> { if (right[0] != null) { if (left[0] != null) { left[0] = right[0] + left[0]; } else { left[0] = right[0]; } }})[0]; 

You can put this in a class if necessary:

  class NullableCollector implements Collector<Double, Double[], Double> { @Override public BiConsumer<Double[], Double> accumulator() { return (left, right) -> { if (right != null) { if (left[0] != null) { left[0] = right + left[0]; } else { left[0] = right; } } }; } @Override public Set<Characteristics> characteristics() { return EnumSet.noneOf(Characteristics.class); } @Override public BinaryOperator<Double[]> combiner() { return (left, right) -> { if (right[0] != null) { if (left[0] != null) { left[0] = right[0] + left[0]; } else { left[0] = right[0]; } } return left; }; } @Override public Function<Double[], Double> finisher() { return (array) -> array[0]; } @Override public Supplier<Double[]> supplier() { return () -> new Double[] { null }; } } 
+4
source

Source: https://habr.com/ru/post/1268280/


All Articles