在使用 Java 的新特性Collectors.toMap() 将 List 转换为 Map 时存在空指针的问题
空指针风险
java.lang.NullPointerException
现象
当 List 中有 null 值的时候,使用 Collectors.toMap() 转为 Map 时,会报 java.lang.NullPointerException
实例
List<TbUserGift> tbUserGiftList = new ArrayList<>();
TbUserGift tbUserGift1 = new TbUserGift();
tbUserGift1.setUserNo("110");
tbUserGift1.setGiftNo("120");
TbUserGift tbUserGift2 = new TbUserGift();
tbUserGift2.setUserNo("999");
tbUserGift2.setGiftNo(null);
tbUserGiftList.add(tbUserGift1);
tbUserGiftList.add(tbUserGift2);
Map<String,String> map = tbUserGiftList.stream().collect(Collectors.toMap(TbUserGift::getName, TbUserGift::getAge));
System.out.println(map);
---------运行错误:java.lang.NullPointerException
at java.util.HashMap.merge(HashMap.java:1225)
at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
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:499)
at OpenGiftTest.testNullPoint(OpenGiftTest.java:201)
.....
原因
原因是声明List集合时有的值为空(如图),但是HashMap中k,v是可以存null值的。
原因是 toMap()
方法中使用 Map.merge()
方法合并时,merge 不允许 value 为 null 导致的,源码如下:
default V merge(K key, @NotNull V value,
@NotNull BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
// 在这里判断了value不可为null
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;
}
}
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
解决方法
- 业务控制不要出现 Null 值【有 Null 的地方,可以赋值默认值】
- 在转换时加判断,如果为 null,则给一个默认值
Map<String, String> map = tbUserGiftList.stream().collect(Collectors.toMap(TbUserGift::getUserNo, tbUserGift -> tbUserGift.getGiftNo() == null ? "0" : tbUserGift.getGiftNo()));
3.使用 collect(…) 构建,允许空值
Map<String, String> map = tbUserGiftList.stream().collect(HashMap::new,(k, v) -> k.put(v.getUserNo(), v.getGiftNo()), HashMap::putAll);// TODO 下游业务从Map取值要做NPE判断
4.使用 Optional 对值进行包装
Map<String, Object> map = tbUserGiftList.stream().collect(Collectors.toMap(TbUserGift::getUserNo, tbUserGift -> Optional.ofNullable(tbUserGift.getGiftNo())));
建议
- 优先业务控制,尽量避免 List 中存在 Null
- 其次推荐第 4 种方法【使用 Optional 对值进行包装】,能很好的避免 NPE 问题