Ank*_*ngh 7 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
Run Code Online (Sandbox Code Playgroud)
或者:
Integer i = s.stream().filter(Objects::nonNull).reduce(Integer::sum).orElse(null);
// returns a value i.e null
Run Code Online (Sandbox Code Playgroud)
检查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);
}
}
Run Code Online (Sandbox Code Playgroud)
在上面的代码中,您会看到get()如果boolean empty为false,则该方法返回一个可选值,在我的情况下,该值为false但state为null,因此Optional.of(null)抛出一个NullPointerException.在我的情况下,我有一个允许的二元运算符null.
所以我认为代码
return empty ? Optional.empty() : Optional.of(state);
Run Code Online (Sandbox Code Playgroud)
应改为
return empty || state == null ? Optional.empty() : Optional.of(state);
Run Code Online (Sandbox Code Playgroud)
作为我的二元运算符(具有减少的任务)并且可以使用null.
您使用的reduce操作的文档说明:
抛出: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();
Run Code Online (Sandbox Code Playgroud)
在这种情况下,NPE被抛到最后一行.
如果您在库中应用了建议的更改,我们将无法区分减少空流与减少结果为空的流的结果.
我真的不能说出为什么必须使用null,这似乎是一个不好的主意。而且,如您所见,您不能reduce使用null作为输入。您可以建立自己的自定义Collector(您不能建立自己的Reducer)。
您所拥有的:
Double result = s.stream()
.filter(Objects::nonNull)
.reduce(Double::sum)
.orElse(null);
Run Code Online (Sandbox Code Playgroud)
是完全正常的 BTW。获得空结果的唯一方法是输入中的所有元素都为空,因此首先过滤它们是一种方法。出于乐趣,我决定编写一个自定义收集器(我猜不出为什么,我想那会很有趣)
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];
Run Code Online (Sandbox Code Playgroud)
如果需要,可以将其放入类本身:
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 };
}
}
Run Code Online (Sandbox Code Playgroud)