起因
编写完一个需求后在联调时出现了一个NPE异常,日志如下
java.lang.NullPointerException: null
at java.util.HashMap.merge(HashMap.java:1226)
at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.Collections$2.tryAdvance(Collections.java:4719)
at java.util.Collections$2.forEachRemaining(Collections.java:4727)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566)
at com.xxx.xxx.service.impl.DynamicFormColumnServiceImpl.lambda$getCodePropertyValue$24(DynamicFormColumnServiceImpl.java:465)
at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1321)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1723)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566)
at com.xxx.xxx.service.impl.DynamicFormColumnServiceImpl.getCodePropertyValue(DynamicFormColumnServiceImpl.java:463)
对应出错的代码如下
sourceDataList.stream().collect(Collectors.toMap(
sourceData -> sourceData,
sourceData -> CommonUtil.convertType(
ReflectUtil.getFieldValue(sourceData, entry.getValue().getRowName()),
entry.getKey().getValueType().getClazz()
)
)
已知sourceDataList
不为空,且其中也没有为null
的元素,并且CommonUtil.convertType
的流程不会抛出异常
分析
根据以上信息分析可以知道,问题就发生在CommonUtil.convertType
的返回结果上
但是根据以往经验,HashMap
是允许一个key
为null
或多个value
为null
的,所以应该是其他的地方没有注意到
查看Collectors.toMap
的调用处,内容如下
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}
发现在Collectors.toMap
的调用过程中并不是我们平常常用的put
方法,而是merge
查看HashMap.merge
方法源码,内容如下
@Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
if (value == null)
throw new NullPointerException();
if (remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
.................
return value;
}
其中明确写到如果value
为空则抛出NPE异常
向上查看Map
接口中的merge
定义,内容如下
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
可以看到判断方式也与HashMap
中重写的方法类似
解决
-
换用
.forEach
方法Map<T, Object> map = new HashMap<>(); sourceDataList.forEach( sourceData -> map.put( sourceData, CommonUtil.convertType( ReflectUtil.getFieldValue(sourceData, entry.getValue().getRowName()), entry.getKey().getValueType().getClazz() ) ) ); return map;
-
写一个自定义的
Collector
(推荐)sourceDataList.stream() .collect(Collector.of( HashMap::new, (map, sourceData) -> map.put( sourceData, CommonUtil.convertType( ReflectUtil.getFieldValue(sourceData, entry.getValue().getRowName()), entry.getKey().getValueType().getClazz() ) ), (o1, o2) -> o1 ))
参考
Collectors.toMap不允许Null Value导致NPE
鸣谢
@黄同学——向黄同学学习!