【问题标题】:NPE on java stream reduce operationjava流减少操作上的NPE
【发布时间】:2017-10-28 10:16:31
【问题描述】:

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

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() 方法在布尔值empty 为false 时返回一个可选值,在我的情况下该值为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

【问题讨论】:

  • Optional.ofNullable 怎么样?无论如何,您的示例看起来不正确,因为您将List&lt;Integer&gt;BinaryOperator&lt;Double&gt; 一起使用。
  • @Pshemo ReducingSink 是一个内部 jdk 类
  • 无论如何-@Pshemo 是对的-该示例编译得不是很好:-)
  • 你浪费了这么多时间研究源代码,而不是一秒钟查看文档,来了解这种行为是故意的......
  • 我知道这种行为是故意的,只是想了解他们的意图

标签: java java-8 java-stream


【解决方案1】:

关于他们决定不处理null 值的原因为什么 我怀疑这与Optional.of 采用强制 值和你必须使用特殊的工厂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

【讨论】:

    【解决方案2】:

    您使用的 reduce 操作的 documentation 声明:

    抛出: NullPointerException - 如果归约的结果为 null

    因此,即使您的二元运算符可以使用 null,您看到的 NPE 也已记录在案并且是预期的结果。

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

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

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

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

    【讨论】:

    • 我同意你的观点,如果应用了建议的更改,那么我们将无法区分减少空流的结果与减少结果为空但可以你建议一个用例。
    • 嗯...一个播放您在 Game X 中令人难以置信的成就的流和一个打印“您的总成就是...”或“您还没有玩过这个游戏,试试看! ",类似的事情:-)
    【解决方案3】:

    我真的不知道为什么您必须使用空值,这似乎是个坏主意。而且,正如您所见,您不能 reduce 使用 null 作为输入。您可以构建自己的自定义Collector(您不能构建自己的Reducer)。

    你有什么:

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

    非常好顺便说一句。获得 null 结果的唯一方法是当输入中的 all 元素为 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 };
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2020-01-05
      • 2023-03-08
      • 2019-02-09
      • 2018-06-15
      • 2015-11-05
      • 1970-01-01
      • 2023-03-31
      • 2019-03-08
      • 1970-01-01
      相关资源
      最近更新 更多