记一次线上环境空指针异常排查
前言
现在,很多小伙伴都在日常开发中使用lambda
表达式,我也经常用,我们线上环境的代码更是广泛使用,而且我之前还给大家分享过一些常用的lambda
表达式,因为lambda
确实很好用,用起来也确实很方便,但是各位小伙伴在使用过程中一定要做好数据校验,避免线上环境出现出现空指针异常,今天我们就来分享下我这一天排查线上环境发现的一个空指针异常(NPE
),这个问题确确实实是那种你不遇到,你根本不知道的异常。话不多说,我们直接开始吧。
问题分析
我们先看这样一段代码:
public static void main(String[] args) {
TestVo testVo = new TestVo();
List<TestVo> testVoList = Lists.newArrayList(testVo);
Map<String, Integer> collectMap = testVoList.stream().collect(Collectors.toMap(TestVo::getName, TestVo::getAge));
System.out.println(collectMap);
}
这段代码没有什么复杂的业务逻辑,咋看没有任何问题,我当初也是这么想的,但是经过我的实际测试,以上代码有两处潜在的空指针异常。
第一处大家应该都可以看出来,就是testVoList
为空的时候,所以在日常开发i中,如果是从数据库查出来的list
,一定要做空指针判断;
另一处我开始也没看出来,直到我做了很多次测试以后,才发现这个隐藏的NPE
。我们先说结论,toMap
方法如果后一个参数(即TestVo::getAge
,对应我们map
的值)为空,整个lambda
就会报空指针异常:
详细分析异常,你会发现,toMap
方法在执行过程中会调用merge
方法,merge
方法要求value
不能为空:
所以各位小伙伴在以后的开发中一定要尽可能避免这两种空指针异常的出现,对于第二种空指针可以通过下面这种方式避免:
Map<String, Integer> collectMap = testVoList.stream().collect(Collectors.toMap(TestVo::getName,
t -> {
if (t.getAge() == null) {
return 0;
} else {
return t.getAge();
}
}));
对于上面的代码,如果把age
的类型改成int
,就不会报错,因为基本类型int
的初始化是0
。所以,这里其实还隐含了第三种空指针异常,包装类转基本类型的空指针异常。
如果你的代码中有,包装类转成基本类型的情况,请务必做好空指针判断,否则也是会有空指针异常的,比如像像下面这段代码:
Integer a = null;
int b = a;
直接运行,就是空指针异常:
所以,各位小伙伴在使用基本类型与包装类的时候,一定要尽可能保持类型一致,如果用包装类就都用包装类,非要做转换的话,就务必做好数据校验。另外,由于基本类型和包装类对java
来说是两种类型,所以下面的方法对java
来说是两个方法,也就是我们说的重载:
private void testInt(Integer i) {
System.out.println(i + i);
}
private void testInt(int i) {
System.out.println(i * i);
}
但这种重载对调用方来说是极其不友好的,我在我们线上环境还真的遇到过,调用哪个方法取决你传的是基本类型还是包装类(谁在实际开发中会关注这个?),所以各位小伙伴一定要避免类似这种方法重载,这种代码一旦出现问题,那就真的把你往死里坑。
总结
作为一个后端开发人员,减少NPE
错误是我们最基本的追求,所以各位小伙伴在实际开发过程中一定要尽可能避免,因为一旦发生NPE
异常,不仅对用户交互不够友好,排查问题也不方便,而且从个人角度来说,不论你能力有多强,贡献有多大,如果你开发的一个核心功能因为没有做数据校验,最后导致NPE
异常,影响系统正常运行,那你怕要背上一个p1
甚至p0
工单,搞不好还会被开除,这样你不觉得冤吗?总之就是,日常开发过程中要尽一切可能避免NPE
异常,不犯这种低级错误,你的职场发展才能走的更顺利。