Java流上的NPE减少操作

时间:2017-05-27 17:37:49

标签: java java-8 java-stream

最近,在使用Java 8流时,我在使用以下测试用例时遇到了NullPointerException的reduce操作:

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 i.e null

或者:

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

检查reduce操作后,我遇到了执行reduce操作的类:

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);
    }
}

在上面的代码中,如果布尔值get()为false,则会看到empty方法返回一个可选值,在我的情况下,值为false,但state为null,所以Optional.of(null)会抛出NullPointerException。在我的例子中,我有一个允许null的二元运算符。

所以我认为代码

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

应改为

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

作为我的二元运算符(具有减少的任务)并且可以使用null

3 个答案:

答案 0 :(得分:7)

您使用的reduce操作的documentation指出:

  

抛出:       NullPointerException - 如果减少的结果为null

因此,即使您的二元运算符与null相符,您看到的NPE也会记录并预期结果。

文档更加冗长,提供了一些等效代码的额外见解:

strptime

在这种情况下,NPE被抛到最后一行。

如果您在库中应用了建议的更改,我们将无法区分减少空流与减少结果为空的流的结果。

答案 1 :(得分:6)

我无法确切地告诉你为什么必须使用null来工作,这似乎是一个糟糕的想法。而且,正如您所见,您不能reduce使用null作为输入。您可以构建自己的自定义Collector(您无法构建自己的Reducer)。

你有什么:

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

非常好 btw。获取null结果的唯一方法是当输入中的所有元素为空时,因此最初过滤它们是最佳选择。为了它的乐趣,我决定写一个自定义收藏家(不能真正告诉为什么,我觉得它会很有趣)

 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];

如果需要,你可以把它放到一个类中:

 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 };
    }

}

答案 2 :(得分:1)

关于为什么他们决定不处理null值的原因为什么我怀疑这与Optional.of具有强制性 value,您必须使用特殊工厂ofNullable来处理实际上是可选的变量... 对于如何实现的实现,我有一个解决方案:有点笨拙,但是您只能使用内置的流转换。
首先,将累加器更改为使用Optional而不是null

private static final BinaryOperator<Optional<Integer>> sum = (a, b) -> a.isPresent()?
        (b.isPresent()? a.map(n -> b.get() + n) : a) : b;

然后将元素包裹在Optional中,然后缩小并解包完成后的结果:

Integer i = s.stream()
   .map(Optional::ofNullable)    // wrap nullables into Optional
   .reduce(sum)                  // get an Optional<Optional<Integer>>
   .flatMap(Function.identity()) // unwrap to an Optional<Integer>
   .orElse(null);                // if stream is empty or reduction is emptyOptional