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;
具体原因推断:
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());
});
评论区