• Collectors.toMap不允许Null Value导致NPE


    背景

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

    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="{@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="{@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遍历列表手动转换。

  • 相关阅读:
    结构体怎么组包发送
    开源语音代码eSpeak1.06 的移植到单片机的过程(二)之分析下speak.c 文件
    看看深圳的房价
    开源语音代码eSpeak1.06 的移植到单片机的过程(一)0之分析下espeak.c 文件
    开源语音代码eSpeak1.06 的学习入门
    利尔达模组CAT1 UIS8910指令的 TCP相关中文解释
    将博客搬至CSDN
    【原创】大叔问题定位分享(39)azkaban定期出现fullgc
    【原创】大叔经验分享(129)mac下启动MAT报错
    【原创】大数据基础之Doris(1)编译安装和启动
  • 原文地址:https://www.cnblogs.com/larva-zhh/p/11394898.html
Copyright © 2020-2023  润新知