三目运算符是咱们常常在代码中使用的,a= (b==null?0:1);这样一行代码能够代替一个if-else,可使代码变得清爽易读。java
可是,三目运算符也是有必定的语言规范的。在运用不恰当的时候会致使意想不到的问题。本文就介绍一个我本身曾经踩过的坑。程序员
1、三目运算符express
对于条件表达式b?x:y,先计算条件b,而后进行判断。若是b的值为true,计算x的值,运算结果为x的值;不然,计算y的值,运算结果为y的值。一个条件表达式从不会既计算x,又计算y。条件运算符是右结合的,也就是说,从右向左分组计算。例如,a?b:c?d:e将按a?b:(c?d:e)执行。app
2、自动装箱与自动拆箱指针
基本数据类型的自动装箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0开始提供的功能。code
通常咱们要建立一个类的对象实例的时候,咱们会这样: Class a = new Class(parameters); 当咱们建立一个Integer对象时,却能够这样: Integer i = 100;(注意:和 int i = 100;是有区别的 )对象
实际上,执行上面那句代码的时候,系统为咱们执行了: Integer i = Integer.valueOf(100); 这里暂且不讨论这个原理是怎么实现的(什么时候拆箱、什么时候装箱),也略过普通数据类型和对象类型的区别。get
咱们能够理解为,当咱们本身写的代码符合装(拆)箱规范的时候,编译器就会自动帮咱们拆(装)箱。那么,这种不被程序员控制的自动拆(装)箱会不会存在什么问题呢?编译器
3、问题回顾hash
首先,经过你已有的经验看一下下面这段代码。若是你获得的结果和后文分析的结果一致(而且你知道原理),那么请忽略本文。若是不一致,请跟我探索下去。
public static void main(String[] args) { Map<String, Boolean> map = new HashMap<>(); Boolean b = map != null ? map.get("test") : false; System.out.println(b); }
以上这段代码,是咱们在不注意的状况下有可能常常会写的一类代码(在不少时候咱们都爱使用三目运算符)。
通常状况下,咱们会认为以上代码Boolean b的最终获得的值应该是null。由于map.get("test")的值是null,而b又是一个对象,因此获得结果会是null。
可是,以上代码会抛出NPE:
Exception in thread "main" java.lang.NullPointerException
首先能够明确的是,既然报了空指针,那么必定是有些地方调用了一个null的对象的某些方法。在这短短的两行代码中,看上去只有一处方法调用map.get("test"),可是咱们也都是知道,map已经事先初始化过了,不会是Null,那么究竟是哪里有空指针呢。
咱们接下来[反编译]一下该代码。看看咱们写的代码在通过编译器处理以后变成了什么样。反编译后代码以下:
public static void main(String args[]){ Map map = new HashMap(); Boolean b = Boolean.valueOf(map == null ? false : ((Boolean)map.get("test")).booleanValue()); System.out.println(b); }
看完这段反编译以后的代码以后,通过分析咱们大概能够知道问题出在哪里。((Boolean)hashmap.get("test")).booleanValue() 的执行过程及结果以下:
hashmap.get("test")->null; (Boolean)null->null; null.booleanValue()->报错
好,问题终于定位到了。很明显,上面源代码中的map.get("test")在被编译成了
(Boolean)map.get("test").booleanValue(),这是一种自动拆箱的操做。
那么,为何这里会发生自动拆箱呢?这个问题又如何解决呢?
4、原理分析
经过查看反编译以后的代码,咱们准确的定位到了问题,分析以后咱们能够得出这样的结论:NPE的缘由应该是三目运算符和自动拆箱致使了空指针异常。
那么,这段代码为何会自动拆箱呢?这实际上是三目运算符的语法规范。参见jls-15.25,摘要以下:
If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.
If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.
If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type.
简单的来讲就是:当第二,第三位操做数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操做。
因此,结果就是:因为使用了三目运算符,而且第2、第三位操做数分别是基本类型和对象。因此对对象进行拆箱操做,因为该对象为null,因此在拆箱过程当中调用null.booleanValue()的时候就报了NPE。
5、问题解决
若是代码这么写,就不会报错:
Map<String,Boolean> map = new HashMap<String, Boolean>(); Boolean b = (map!=null ? map.get("test") : Boolean.FALSE);
就是保证了三目运算符的第二第三位操做数都为对象类型。这样就不会发生自动拆箱操做,以上代码获得的b的结果为null。
PS:本文中的示例,只是为了更加方便读者理解三目运算符会致使自动拆箱现象,可能在代码中并不会直接这样使用。可是,我本身的代码确实发生过相似问题。这里简化一下,为了讲清楚原理。