线上问题:Stream toMap value为null时抛空指针

1 问题复现

使用stream的toMap方法从List<对象A>转Map<String, String>,
当对象A中的属性值为null的属性转换为Map的value时,使用toMap会抛出异常。
即:
对象:UserEntity属性nickname值为null,将nickname作为Map的value,uid作为key,抛异常的代码片段如下:

List<UserEntity> userEntityList1 = new ArrayList<>();
        userEntityList1.add(new UserEntity("1", null, "male"));
        Map<String, String> map1 = new HashMap<>();
        map1 = Optional.ofNullable(userEntityList1).orElse(new ArrayList<>()).stream().collect(Collectors.toMap(UserEntity::getUid, UserEntity::getNickname, (oldVal, newVal) -> oldVal));
  • 测试样例
package lambda_expression;

import common.entity.BaseUserEntity;
import common.entity.UserAgeEntity;
import common.entity.UserEntity;

import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Stream数据转换测试.
 *
 * @author xindaqi
 * @date 2021-06-28 17:20
 */
public class DataTransformStreamTest {

    private static final Logger logger = Logger.getLogger("StreamTest");

    public static void main(String[] args) {

        List<UserEntity> userEntityList1 = new ArrayList<>();
        userEntityList1.add(new UserEntity("1", null, "male"));
        Map<String, String> map1 = new HashMap<>();
        map1 = Optional.ofNullable(userEntityList1).orElse(new ArrayList<>()).stream().collect(Collectors.toMap(UserEntity::getUid, UserEntity::getNickname, (oldVal, newVal) -> oldVal));
        logger.info("map1:" + map1);
        if(null != map1.get("1")) {
            logger.info("map1 not empty");
        } else {
            logger.info("map1 is empty");
        }
    }
}
  • 异常
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 lambda_expression.DataTransformStreamTest.main(DataTransformStreamTest.java:176)

2 原因

toMap源码:

public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction) {
        return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
    }
    
public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction,
                                Supplier<M> mapSupplier) {
        BiConsumer<M, T> accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
        
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;
    }
  • 分析
    由源码可知,toMap的中调用的merge方法中对象要求:Objects.requireNonNull(value);可知,Map的value不可为null,因此,使用toMap方式转换Map时,值value不能为null。

3 方案

使用collect(HashMap::new…)方案,避免值为null抛空指针。

map1 = Optional.ofNullable(userEntityList1).orElse(new ArrayList<>()).stream().collect(HashMap::new, (k, v) -> k.put(v.getUid(), v.getNickname()), HashMap::putAll);

4 附

当key为null时,使用toMap不会抛空指针。


版权声明:本文为Xin_101原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。