在代码中使用了三目运算符,代码在线上运行的时候发生了NPE,经过排查,发现原来是三目运算符和自动拆装箱之间有一定的关系,导致了空指针。那么,三木是三目运算符呢?为什么会导致空指针?下面也是学习了解到的内容,跟大家分享下。
一、三目运算符
对于条件表达式 b ? x : y,先计算条件b,然后进行判断。如果b的值为true,计算x的值,运算结果为x的值;否则,计算y的值,运算结果为y的值。一个条件表达式从不会既计算x,又计算y。条件运算符是右结合的,也就是说,从右向左分组计算。例如,a ? b : c ? d : e 将按a ? b :(c ? d : e)执行。
二、自动装箱与自动拆箱
基本数据类型的自动装箱(autoboxing)、拆箱(unboxing) 是自J2SE 5.0开始提供的功能。
一般我们要创建一个类的对象实例的时候,我们会这样: Class a = new Class(parameters);
当我们创建一个Integer 对象时,却可以这样: Integer i = 100;( 注意:和 int i = 100; 是有区别的)
实际上,执行上面那句代码的时候,系统为我们执行了: Integer i = Integer.valueOf(100);
这里暂且不讨论这个原理是怎么实现的(何时拆箱、何时装箱),也略过普通数据类型和对象类型的区别。
我们可以理解为,当我们自己写的代码符合装(拆)箱规范的时候,编译器就会自动帮我们拆(装)箱。那么,这种不被程序员控制的自动拆(装)箱会不会存在什么问题呢?
三、问题回顾
首先,看如下代码
Map<String,Boolean> map = new HashMap<String, Boolean>();
Boolean b = (map!=null ? map.get("test") : false);
以上这段代码,是我们在不注意的情况下有可能经常会写的一类代码。但是,这段代码是存在问题的,执行该代码,会报NPE。
Exception in thread "main" java.lang.NullPointerException
在这短短的两行代码中,看上去只有一处方法调用map.get(“test”),但是我们也都是知道,map 已经事先初始化过了,不会是Null,那么到底是哪里有空指针呢。
我们接下来反编译一下该代码。看看我们写的代码在经过编译器处理之后变成了什么样。
HashMap hashmap = new HashMap();
Boolean boolean1 = Boolean.valueOf(hashmap == null ? false : ((Boolean)
hashmap.get("test")).booleanValue());
可以看出是hashmap.get(“test”)).booleanValue())这里出了问题,hashmap.get(“test”))的结果是null,所以null.booleanValue()自然就NPE了。
四、原理分析
通过查看反编译之后的代码,我们准确的定位到了问题,分析之后我们可以得出这样的结论:NPE 的原因应该是三目运算符和自动拆箱导致了空指针异常。
根据规定,三目运算符的第二、第三位操作数的返回值类型应该是一样的,这样才能当把一个三目运算符的结果赋值给一个变量。
如:Person i = a>b : i1:i2; ,就要求i1 和i2 的类型都必须是Person才行。
因为Java 中存在一种特殊的情况,那就是基本数据类型和包装数据类型可以通过自动拆装箱的方式互相转换。即可以定义int i = new Integer(10);也可以定义Integer i= 10;
那如果,三目运算符的第二位和第三位的操作数的类型分别是基本数据类型和包装类型对象时,就需要有一方需要进行自动拆装箱。
所以为了避免NPE,要按下面这样写才不会出问题:
Map<String,Boolean> map = new HashMap<String, Boolean>();
Boolean b = (map!=null ? map.get("test") : Boolean.FALSE);