Collectors.toMap不允许Null Value导致NPE

摘要:
超V,?超V,?
背景

线上某任务出现报警,报错日志如下:

java.lang.NullPointerException: null
        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:1380)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
        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.xxx.web.controller.TaskController.getOmsAccidCloudCCUserIdMap(TaskController.java:648)
        at com.xxx.web.controller.TaskController.executePushNewRegisterLead(TaskController.java:400)
        at com.xxx.web.controller.TaskController.pushNewRegister(TaskController.java:145)

对应出错的代码:

omsAccidCloudCCUserIdMap = administratorList.stream()
.collect(Collectors.toMap(Administrator::getAccid,administrator 
-> cloudccAccidUserIdMap.get(administrator.getCloudccAccid())));

已知administratorList不含有null元素,administratorListcloudccAccidUserIdMap都不为nullAdministrator::getAccid也不会返回null值。

问题定位

综上所述,NPE只可能发生在

administrator -> cloudccAccidUserIdMap.get(administrator.getCloudccAccid())

但是HashMap是允许一个null key和多个null value的啊,查看openjdk的HashMap源码,看到javadoc也是如此说明的:

Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.

那究竟是哪里出了问题呢,回过头仔细看报错信息,发现是java.util.HashMap.merge(HashMap.java:1225)抛出的异常,查看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();
        ...     
         return value;
    }

向上追溯,看到java.util.Map接口中对merge的定义:

/**
     * If the specified key is not already associated with a value or is
     * associated with null, associates it with the given non-null value.
     * Otherwise, replaces the associated value with the results of the given
     * remapping function, or removes if the result is {@code null}. This
     * method may be of use when combining multiple mapped values for a key.
     * For example, to either create or append a {@code String msg} to a
     * value mapping:
     *
     * <pre> {@code
     * map.merge(key, msg, String::concat)
     * }</pre>
     *
     * <p>If the function returns {@code null} the mapping is removed.  If the
     * function itself throws an (unchecked) exception, the exception is
     * rethrown, and the current mapping is left unchanged.
     *
     * @implSpec
     * The default implementation is equivalent to performing the following
     * steps for this {@code map}, then returning the current value or
     * {@code null} if absent:
     *
     * <pre> {@code
     * V oldValue = map.get(key);
     * V newValue = (oldValue == null) ? value :
     *              remappingFunction.apply(oldValue, value);
     * if (newValue == null)
     *     map.remove(key);
     * else
     *     map.put(key, newValue);
     * }</pre>
     *
     * <p>The default implementation makes no guarantees about synchronization
     * or atomicity properties of this method. Any implementation providing
     * atomicity guarantees must override this method and document its
     * concurrency properties. In particular, all implementations of
     * subinterface {@link java.util.concurrent.ConcurrentMap} must document
     * whether the function is applied once atomically only if the value is not
     * present.
     *
     * @param key key with which the resulting value is to be associated
     * @param value the non-null value to be merged with the existing value
     *        associated with the key or, if no existing value or a null value
     *        is associated with the key, to be associated with the key
     * @param remappingFunction the function to recompute a value if present
     * @return the new value associated with the specified key, or null if no
     *         value is associated with the key
     * @throws UnsupportedOperationException if the {@code put} operation
     *         is not supported by this map
     *         (<a href="http://t.zoukankan.com/{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
     * @throws ClassCastException if the class of the specified key or value
     *         prevents it from being stored in this map
     *         (<a href="http://t.zoukankan.com/{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
     * @throws NullPointerException if the specified key is null and this map
     *         does not support null keys or the value or remappingFunction is
     *         null
     * @since 1.8
     */
    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;
    }

可以看到,Collectors.toMap在底层使用的是Map::merge方法,而merge方法不允许null value,无论该Map实例是否支持null value(但是允许null key如果Map实例支持的话)。

@throws NullPointerException if the specified key is null and this map does not support null keys or the value or remappingFunction is null

问题解决

找到原因了,那么如何解决呢?这个问题在stackoverflow上也有一篇帖子说明了如何fix这个问题https://stackoverflow.com/questions/24630963/java-8-nullpointerexception-in-collectors-tomap
不使用Collectors.toMap,改为使用forEach遍历列表手动转换。

免责声明:文章转载自《Collectors.toMap不允许Null Value导致NPE》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Redis源码解析03: 字典的遍历C#委托和事件详解下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

面试了一个 31岁的iOS开发者,思绪万千,30岁以上的程序员还有哪些出路?

前言之前HR给了我一份简历,刚看到简历的第一眼,31岁?31岁,iOS开发工程师,工作经历7年,5年左右都在外包公司,2年左右在创业公司。iOS开发工程师这块,还是很少遇到30岁以上的开发,正好,来了一个30岁的开发,说实话,对我来说,还是蛮期待的,希望对我有所启示。这样的过程持续了半个小时那么年过350岁的程序员还有出路吗?作为一个8年的iOS开发,而且几...

开源跳板机jumpserver的安装部署和使用详细教程及踩坑经验

安装篇jumpserver需要依赖于mysql数据库,python开发工具的支持,所以需要安装一系列软件。按照提示进行所有流程的安装,安装完成之后访问http://ip:8000端口即可登录到jumpserver。因为jumpserver会在被管理的后端主机上通过此处指定的管理用户来添加指定的用户和sudo权限:配置sudo授权,用于添加sudo授权。...

Json对象转Ts类

其次,Json是一种轻量级的数据交换格式。在前端和后端之间的数据交互过程中,后端接口返回Json格式的数据,前端需要使用相应的Ts类对象来接收它。此时,如果后端提供样本数据或现有接口返回的Json格式数据,是否有方法帮助我们从Json格式数据生成Ts类?介绍了三个主要功能。1.查看Json对应的Ts类,将要格式化的Json字符串复制粘贴到中间编辑区域。单击右...

H3C 12508 收集诊断信息

案例:H3C12508单板卡出现remove状态,需要配合研发收集诊断信息。)总体:12500交换机返回三种文件----故障时诊断信息,主备单板的日志文件,主备单板的诊断日志操作步骤:一、故障时诊断信息:disdiagnostic-informationdiag收集必须在问题出现的时候,单板重起之前执行。在save时请选择Y保存到CF卡方式。一般情况下,此命...

CUPS

杯子:一个。工具1.hal设备管理器2.系统配置打印机3.Web管理器/etc/cups/ccups。conf/etc/cups/printer conf II。打印机本地安装和客户端安装1.在本地安装Linux打印机时,应选择postscript和pcl打印机。如果没有,则应将打印机设置为原始打印模式/etc/cups/printers。有限公司...

js学习-es6实现枚举

最近,我大部分时间都在写dart,突然使用了js。我发现js不能直接声明枚举。目录枚举功能对象冻结()符号实现反映了不可更改值的唯一性。请注意,枚举特性枚举值不能重复,也不能修改。Switchcase可以直接判断对象。冻结()对象。方法可以冻结对象。无法更改实现constEnumSex=Object。冷冻枚举性别。人=1;安慰日志;//符号(男性)表示值co...