场景
一次线上问题,出现了NPE,当时的代码类似如下:
1. Map<String, 业务对象> map = xxxService.findXxx(xxx);
2. Long result = map.get(xxx) != null ? map.get(xxx).getId() : 0L;
复制代码
就是第二行出错了,正常来说,当map.get(xxx)
时,咱已经判断了null了,只有非null才会get呀,怎么还会出现null异常呢?
排查问题的过程当中,搜到了这么一篇博客:blog.csdn.net/prestonzzh/…
大致的意思是说使用三元运算符的时候,CPU会为了速度更快,会将三元运算符两边的结果都计算出来,
这问题就大了,用了这么久的三元运算符也没出现过这种问题呀,搜索网络上也咩有人碰到过这种坑,并且java里的三元运算符也是生成字节码运行的,没有直接和CPU打交道,那咱去看看字节码是啥样子的吧:
public static void main(String[] args) {
boolean condition = false;
Long data = 2L;
Long res = condition ? data : 0L;
System.out.println(res);
}
复制代码
可以看到,三元运算符编译成字节码后,并不是三元运算符了,也是通过IFEQ
、GOTO
这种类似if else执行相应的分支的,
这说明因为CPU的优化导致的两边都执行出现的NPE错误站不住脚,程序肯定是先判断条件,再根据条件走分支的,
此处细心一点我们可以看见,在字节码中出现了Long.longValue
、Long.valueOf
这种字眼,这里不卖关子了,
这涉及到java中的拆箱装箱
概念,当基本类型和包装类型在一起时,包装类型会被自动转换成基本类型
,也就是说,上面的data和0L在一起时,
data在字节码中会被自动变成data.longValue()
变成基本类型,也就是拆箱,所以这时候是很危险的,如果data是null的话,岂不是会出错?我们验证一下:
可以看见,直接出现了NPE, 这说明包装类型和基本类型在一起的时候是有坑的,一不小心包装类型是null,在语法阶段没什么问题,但是到运行阶段出NPE的错误了,
这时候我们可以把0L变成包装类型,这时候java就不会自动帮我们变成基本类型了,因为两个都是包装类型就不会拆箱了,
顺利输出null,
可以看到字节码中现在已经没有Long.longValue
、Long.valueOf
这种字眼了,说明没有被拆箱。
问题现象解决
顺着上面出现拆箱装箱问题,我怀疑是map.get(xxx)
获取到的值不为null,但是getId()
属性获取到的是null,所以出现了拆箱,导致了NPE,
1. Map<String, 业务对象> map = xxxService.findXxx(xxx);
2. Long result = map.get(xxx) != null ? map.get(xxx).getId() : 0L;
复制代码
最后根据查看xxxService.findXxx(xxx)
代码时发现,如果没有找到xxx
相应的信息时,就new了一个业务对象返回回来,但是new出来的对象里的值都是null,所以出现了拆箱导致了NPE,
这个问题其实就是拆箱装箱导致的,远没有到CPU层面~ 日后开发过程中多注意细节,不能轻易怀疑CPU,哈哈~