Collectors.toMap报错:空指针 & key重复

Collectors.toMap报错:空指针 & key重复_第1张图片

Java 8中的stream在项目开发中被同学们用的风生水起,当然大家也踩了不少坑。下面我就来说说Collections.toMap在项目使用中踩的坑,避免大家重复被坑。


一.介绍Collectors.toMap

Collectors.toMap 是 Java 8 中的一个收集器,它可以将流中的元素转换为 Map 对象,其中每个元素的 key 由指定的函数生成。

当我们使用 Collectors.toMap 方法时,可能会遇到重复的 key 问题,这是因为我们在将元素转化为 Map 对象时,如果两个元素具有相同的 key,则会发生冲突,抛出异常。

还可能会遇到value为null的问题,这是因为我们在将元素转化为 Map 对象时,toMap最终是调用了Map.merge方法,merge方法不允许value为null 导致的异常抛出。

二.问题复现与分析以及解决方案

1、Collectors.toMap的key重复问题

问题复现:
    public static void main(String[] args) {
        List benefitModelList = new ArrayList<>();
        benefitModelList.add(new BenefitModel("123", "积分权益"));
        benefitModelList.add(new BenefitModel("123", "现金权益"));
        Map benefitMap = benefitModelList.stream().collect(Collectors.toMap(BenefitModel::getBenefitId, BenefitModel::getBenefitName));
        System.out.println(JSON.toJSONString(benefitMap));
    }
运行结果:

Collectors.toMap报错:空指针 & key重复_第2张图片

原因分析:

查看Collectors.toMap源码如下,

Collectors.toMap报错:空指针 & key重复_第3张图片

Collectors.toMap报错:空指针 & key重复_第4张图片

toMap最终是调用了Map.merge方法,传入的mergeFunction是throwingMerger直接抛出异常,日志信息使用的是第一个参数u。传入的mapSupplier是HashMap对象(HashMap::new)。所以最终会调用到HashMap.merge。

而在HashMap.merge中,对于mergeFunction的应用如下:

Collectors.toMap报错:空指针 & key重复_第5张图片

在HashMap.merge的语义中,mergeFunction用于合并value,比如对于key的计数,可以使用map.merge(key, 1, Integer::sum)。若不存在则置1,存在则+1。这里的入参是oldValue和newValue。

所以最终传递给throwingMerger的两个参数就不是k-v了。所以报错的所谓Duplicate key其实是oldValue。

解决方案:
  • 保证toMap的key不重复
  • 调用重载方法,主动指定当key重复时,需要做的合并操作(合并规则可以根据业务需要,自定义)

Collectors.toMap报错:空指针 & key重复_第6张图片

于是上面重复key的代码优化后为:(合并规则:重复key出现时,取后面的,前面的丢弃)

    public static void main(String[] args) {
        List benefitModelList = new ArrayList<>();
        benefitModelList.add(new BenefitModel("123", "积分权益"));
        benefitModelList.add(new BenefitModel("123", "现金权益"));
        Map map = benefitModelList.stream()
                .collect(Collectors.toMap(BenefitModel::getBenefitId, BenefitModel::getBenefitName,
                                          (k1, k2) -> k2));
        System.out.println(JSON.toJSONString(map));
    }
高版本JDK的修复措施:

重复key这个问题在后续版本中得到修复,比如在JDK 11中的处理。

Collectors.toMap报错:空指针 & key重复_第7张图片

Collectors.toMap报错:空指针 & key重复_第8张图片

2、Collectors.toMap的value值为null问题

问题复现:
    public static void main(String[] args) {
        List benefitModelList = new ArrayList<>();
        benefitModelList.add(new BenefitModel("123", "积分权益"));
        benefitModelList.add(new BenefitModel("124", null));
        Map benefitMap = benefitModelList.stream().
        collect(Collectors.toMap(BenefitModel::getBenefitId, BenefitModel::getBenefitName));
        System.out.println(JSON.toJSONString(benefitMap));
    }
运行结果:

Collectors.toMap报错:空指针 & key重复_第9张图片

原因分析:

有问题,看源码,查看Collectors.toMap源码如下,

Collectors.toMap报错:空指针 & key重复_第10张图片

Collectors.toMap报错:空指针 & key重复_第11张图片

toMap最终是调用了Map.merge方法,而在HashMap.merge中,对于value的应用如下:

Collectors.toMap报错:空指针 & key重复_第12张图片

Collectors.toMap报错:空指针 & key重复_第13张图片

在HashMap.merge的语义中,value使用前需要进行判空处理,null直接抛出异常NullPointerException。

解决方案:

方案1:先把value为null的数据过滤掉,再用Collectors.toMap。

        Map map2 = benefitModelList.stream()
                .filter(m -> m.getBenefitName() != null)
                .collect(Collectors.toMap(BenefitModel::getBenefitId, BenefitModel::getBenefitName));

方案2:查资料评价度最好的方案如下。其实跟你方案1中思路-手动foreach一毛一样。

        Map map2 = benefitModelList.stream().collect(HashMap::new, 
                (m, v) -> m.put(v.getBenefitId(), v.getBenefitName()),
                HashMap::putAll);
高版本JDK的修复措施:

Collectors.toMap使用时,value值为null,这个问题在Java 11中仍然存在。可能value为null,这种数据很少见,促使解决过程比较缓慢。

三、Collectors.toMap使用总结

综上所以,在使用Collectors.toMap时需要记住几点:

1、key不能有重复,否则会报错IllegalStateException: Duplicate key,因为Map的key不能重复。

2、value不能为空,否则报错NullPointerException。

看完了本文,你可以去搜搜你的项目代码中使用Collectors.toMap的地方,有没有可能踩上面的坑。不要说你的业务数据不会出现重复key的数据,不会出现value值null的情况,上百万的业务数据,什么情况都会有的。

参考资料:java - Ignore duplicates when producing map using streams - Stack Overflow

java - NullPointerException in Collectors.toMap with null entry values - Stack Overflow

你可能感兴趣的:(后端开发,java,开发语言,空指针,key重复,Map,对象)