Collectors.toMap 报错 NullPointerException

2022-08-02 13:38:47

最近线上偶尔会报一个 NPE,是Collectors.toMap 导致的,这里小记一下,防止再次踩坑。

场景:批量查询用户信息,查询结果为List<User>,然后将其转换成Map<Integer, String>,以供其他地方使用,但在Collectors.toMap 时抛出了异常NullPointerException

复现问题

publicclassToMapTest{publicstaticvoidmain(String[] args){List<User> users=query();Map<Integer,String> userMap= users.stream().collect(Collectors.toMap(User::getId,User::getGirlfriend));}publicstaticList<User>query(){List<User> list=newArrayList<>();// girlfriend 为 null
        list.add(newUser(1,null));return list;}}@DataclassUser{privatefinalInteger id;privatefinalString girlfriend;}

User 的 girlfriend 为 null 时,会抛出 NPE(单身狗落泪!!!)

Exception in thread "main" 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 com.example.collectors.ToMapTest.main(ToMapTest.java:19)

原因

在第 16 行,会调用map.merge,且两个参数的toMap 方法默认是创建HashMap(第 5 行)。

// Collectors.toMappublicstatic<T,K,U>Collector<T,?,Map<K,U>>toMap(Function<?superT,?extendsK> keyMapper,Function<?superT,?extendsU> valueMapper){returntoMap(keyMapper, valueMapper,throwingMerger(),HashMap::new);}// Collectors.toMappublicstatic<T,K,U,MextendsMap<K,U>>Collector<T,?,M>toMap(Function<?superT,?extendsK> keyMapper,Function<?superT,?extendsU> valueMapper,BinaryOperator<U> mergeFunction,Supplier<M> mapSupplier){BiConsumer<M,T> accumulator// 调用 map.merge 方法=(map, element)-> map.merge(keyMapper.apply(element),
                                      valueMapper.apply(element), mergeFunction);returnnewCollectorImpl<>(mapSupplier, accumulator,mapMerger(mergeFunction), CH_ID);}

原因很简单,第 4 行判断了value == null,所以抛出了NullPointerException

@OverridepublicVmerge(K key,V value,BiFunction<?superV,?superV,?extendsV> remappingFunction){if(value==null)thrownewNullPointerException();if(remappingFunction==null)thrownewNullPointerException();......}

解决办法

以下两种方法与Collectors.toMap 方法的区别是:当出现重复的 key 时,Collectors.toMap 默认会抛异常,而以下两种方式会用新值覆盖旧值。

// Collectors.toMap 默认抛异常privatestatic<T>BinaryOperator<T>throwingMerger(){return(u,v)->{thrownewIllegalStateException(String.format("Duplicate key %s", u));};}

Stream.collect 方法

Streamcollect 可以实现简易的Collector 功能:

// Stream.collect()<R>Rcollect(Supplier<R> supplier,BiConsumer<R,?superT> accumulator,BiConsumer<R,R> combiner);publicstaticvoidmain(String[] args){List<User> users=query();Map<Integer,String> userMap2= users.stream().collect(HashMap::new,(map, user)-> map.put(user.getId(), user.getGirlfriend()),HashMap::putAll);System.out.println(userMap2);}// 输出// {1=null}

直接使用 Collector

与使用Stream.collect 方法相比,Collector多了一个Characteristics 参数,这里用不到所以给了空数组:

publicstaticvoidmain(String[] args){List<User> users=query();Collector<User,Map<Integer,String>,Map<Integer,String>> collector=Collector.of(HashMap::new,(map, user)-> map.put(user.getId(), user.getGirlfriend()),(map1, map2)->{
            map1.putAll(map2);return map1;},newCollector.Characteristics[]{});Map<Integer,String> userMap2= users.stream().collect(collector);System.out.println(userMap2);}// 输出// {1=null}
  • 作者:赵丙双
  • 原文链接:https://blog.csdn.net/qq_20919883/article/details/119654002
    更新时间:2022-08-02 13:38:47