侧边栏壁纸
博主头像
这就是之谦博主等级

我们的征途是星辰大海

  • 累计撰写 182 篇文章
  • 累计创建 3 个标签
  • 累计收到 16 条评论
标签搜索

目 录CONTENT

文章目录

Collectors.toMap空指针问题

这就是之谦
2023-04-13 / 0 评论 / 0 点赞 / 774 阅读 / 773 字
温馨提示:
本文最后更新于 2023-04-13,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Collectors.toMap空指针问题

线上这么一段代码,爆出了空指针。

lambda表达式报错不会把堆栈打出来,很头疼。

先看代码:

public static void main(String[] args) {

    List<ShopServiceResponse> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        ShopServiceResponse response = new ShopServiceResponse();
        response.setSkuId("kkk" + i);
        list.add(response);

    }
    Map<String, Long> collect = list.stream().collect(Collectors.toMap(ShopServiceResponse::getSkuId,ShopServiceResponse::getCost,(f1,f2)->f1));
    System.out.println(collect);
}

@Data
public static class ShopServiceResponse {
    private String skuId;
    private Long cost;
}

重点在Collectors.toMap方法。当cost为null的时候,会报错?

value=null在hashmap里不是很正常吗?HashMap是允许一个null key和多个null value的啊

看报错,Collectors.toMap使用的是HashMap的merge方法,不允许value=null;

image-20230413153325530

具体原因推断:

Collectors.toMap可以使用ConcurrentHashMap为最终收集结构,而ConcurrentHashMap不允许Value为Null避免产生二义性和CAS的ABA问题,所以Map.merge为了兼容ConcurrentHashMap等多线程环境下使用的数据结构不允许value为null。
ConcurrentHashMap key和value不能null
如果允许value为null,通过key查找的时候得到null,有两种含义:1. key对应的value为null;2. map中不存在这个key。

单线程场景下的HashMap,允许value为null,是因为可以用map.containsKey(key)来区分上面两种含义,可以避免二义性。

但是多线程下,调用get(key)和containsKey(key)不是原子的,无法避免二义性。例如,线程A调用concurrentHashMap.get(key)方法,返回为null,我们还是不知道这个null是没有映射的null还是存的值就是null。我们假设此时返回为null的真实情况就是因为这个key没有在map里面映射过。那么我们可以用concurrentHashMap.containsKey(key)来验证我们的假设是否成立,我们期望的结果是返回false。但是在我们调用concurrentHashMap.get(key)方法之后,containsKey方法之前,有一个线程B执行了concurrentHashMap.put(key,null)的操作。那么我们调用containsKey方法返回的就是true了。这就与我们的假设的真实情况不符合了。也就是上面说的二义性。

参考:https://www.cnblogs.com/fanguangdexiaoyuer/p/12335921.html

那现在存在了value的场景怎么解决?

1、value=null代表数据无效,直接忽略掉这部分数据;

2、value=null的数据需要保留。

对于1 的话,我们直接判断下是否=null,然后剔除即可。

Map<String, Long> collect = list.stream().filter(p->p.getCost()!=null).collect(Collectors.toMap(ShopServiceResponse::getSkuId,ShopServiceResponse::getCost,(f1,f2)->f1));

对于2 的话,我们可以用一种不太优雅的方式解决

Map<String, Long> collect = list.stream().collect(HashMap::new, (k,v)->k.put(v.getSkuId(),v.getCost()),HashMap::putAll);

或者直接抛弃lambda,使用for循环,这样看起来感觉更优雅

Map<String, Long> collect = new HashMap<>();
list.forEach(p->{
    collect.put(p.getSkuId(),p.getCost());
});
0

评论区