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遍歷列表手動轉換。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM