这里写目录标题
1. 问题描述
- 页面刷新时报服务器内部错误提示;
- 查看日志发现是使用
Stream
的Collectors.toMap
时报NullPointerException
异常;
2. 背景
- 资源关联用户时,在设计时,没有冗余用户名称,而在页面列表中需要展示用户名称;
- 查看了代码实现,没有使用连表查询,而是分几步查询:
- 先查询出资源,过滤获取到用户id集合;
- 根据用户id集合查询用户信息,然后转成Map,key为id,value为name;
- 然后在遍历资源列表,根据id对名称进行赋值;
- 因测试操作,出现了用户没有用户名的情况,因此在转成Map时,报NPE错误;
- 和产品沟通后,针对名称为null的用户,展示用户账号,如果账号也为空,则不展示;
- jdk版本:1.8
3. 原因
Stream
流的Collectors.toMap
方法,key
和value
都不能为空;Collectors
类中有好几个重载的toMap
方法,我们简单的看一下源码;
3.1 Collectors的toMap方法(只有key和value参数的)
- 在toMap的源码中,中调用了
uniqKeysMapAccumulator
方法对key和value进行了处理;
- 在
uniqKeysMapAccumulator
的源码中,通过Objects.requireNonNull(valueMapper.apply(element))
对valueMapper.apply(element)
的返回值进行了空值判断,如果为空,则抛出NEP异常;
3.2 Collectors的toMap方法(带key去重校验的参数)
- 在toMap的源码中,调用了重载的toMap方法,
- 在重载的
toMap
方法中,声明了Biconsume
r函数表达式,在函数表达式中调用了map的merger方法,此时的map为HashMap
类型,是由上一步调用时传递的HashMap::new
- 在
HashMap
的merge
方法中,判断了如果value
为空,则抛出NPE
问题
4. 解决方法
- 增加
peek
处理,判断如果名称为null,则把账号赋值给名称; - 增加
filter
处理,过滤掉name为null的值; - 代码如下:
@Data
public class IdAndNameVo {
@ApiModelProperty(value = "id")
private Integer id;
@ApiModelProperty(value = "名称")
private String name;
@ApiModelProperty(value = "名称")
private String account;
@ApiModelProperty(hidden = true)
public static Map<Integer, String> toIdNameMap(List<IdAndNameVo> mouldIdNameVos) {
if (mouldIdNameVos == null) {
return new HashMap<>();
}
return mouldIdNameVos.stream()
.peek(v -> {
if (v.getName() == null) {
v.setName(v.getAccount());
}
})
.filter(v -> v.getName() != null)
.collect(Collectors.toMap(IdAndNameVo::getId, IdAndNameVo::getName, (v1, v2) -> v1));
}
}
5. 小结
HashMap
的key
不能为空,value
可以为空;- 用
Collectors.toMap
返回的HashMap
,key
不能为空,value
也不能为空; - 编码要谨慎,产品要求名称不能为空的,单还是出现了异常;