当前位置: 首页 > 科技观察

你真的了解Java中的三元运算符吗?

时间:2023-03-12 21:53:18 科技观察

三元运算符就是我们在代码中经常用到的,a=(b==null?0:1);这样一行代码可以代替一个if-else,这样可以使代码清晰易读。但是,三元运算符也有一定的语言规范。如果使用不当,可能会导致意想不到的问题。本文介绍一个我曾经踩过的坑。1、三元运算符对于条件表达式b?x:y,先计算条件b,再进行判断。如果b的值为真,则计算x的值,运算结果为x的值;否则,计算y的值,运算结果为y的值。条件表达式永远不会同时计算为x和y。条件运算符是右结合的,即从右到左分组和求值。例如,a?b:c?d:e将被执行为a?b:(c?d:e)。2.自动装箱和自动拆箱基本数据类型的自动装箱和拆箱是J2SE5.0开始提供的功能。通常,当我们要创建一个类的对象实例时,我们会这样做:Classa=newClass(parameters);当我们创建一个Integer对象时,我们可以这样做:Integeri=100;(注意:和inti=100;是有区别的)其实上面这段代码执行的时候,系统已经帮我们执行了:Integeri=Integer.valueOf(100);fashionbox),也略过了普通数据类型和对象类型的区别。我们可以这样理解,当我们自己写的代码符合装箱(拆箱)规范时,编译器会自动帮我们拆箱(装箱)。那么,这种不受程序员控制的自动拆包(打包)会不会有什么问题呢?3.问题回顾首先,根据自己已有的经验来看一下下面的代码。如果你得到的结果和后面的分析结果一致(而且你知道原理),那么请忽略这篇文章。如果没有,请和我一起探索。publicstaticvoidmain(String[]args){Mapmap=newHashMap<>();Booleanb=map!=null?map.get("test"):false;System.out.println(b);}上面的代码是我们可能经常不注意写的一类代码(很多时候我们爱用三元运算符)。一般来说,我们认为上面代码中Booleanb的最终值应该是null。因为map.get("test")的值为null,而b是一个对象,所以结果将为null。但是,上面的代码会抛出NPE:Exceptioninthread"main"java.lang.NullPointerException首先要明确的是,既然报了空指针,肯定是某处调用了空对象的一些方法。在这短短的两行代码中,好像只有一个方法调用了map.get("test"),但是我们都知道map已经提前初始化好了,不会为Null,那么哪里是null呢指针。接下来我们反编译代码。看看我们写的代码经过编译器处理后变成了什么。反编译后的代码如下:);System.out.println(b);}看完这段反编译后的代码,经过分析我们大概可以知道问题出在哪里了。((Boolean)hashmap.get("test")).booleanValue()的执行过程和结果如下:publicstaticvoidmain(Stringargs[]){Mapmap=newHashMap();Booleanb=Boolean.valueOf(map==null?false:((Boolean)map.get("test")).booleanValue());System.out.println(b);}好了,问题终于定位了。很明显,上面源码中的map.get("test")被编译成了(Boolean)map.get("test").booleanValue(),这是一个自动拆箱的操作。那么为什么自动拆箱发生在这里呢?如何解决这个问题呢?4.原理分析通过查看反编译后的代码,我们准确定位到了问题所在。经过分析,我们可以得出结论,NPE的原因应该是三元运算符和自动拆箱导致了空指针异常。那么,为什么这段代码会自动拆箱呢?这其实就是三元运算符的语法规范。参见jls-15.25,总结如下:如果第二个和第三个操作数的类型相同(可能是null类型),那么就是条件表达式的类型。如果第二个和第三个操作数之一是基本类型T,而另一个的类型是对T应用装箱转换(§5.1.7)的结果,则条件表达式的类型是T。如果其中一个第二个和第三个操作数是空类型,另一个是引用类型,那么条件表达式的类型就是那个引用类型。简单来说:当第二个和第三个操作数分别是原始类型和对象时,对象将被拆箱为基本类型进行操作。所以,结果是:由于使用了三元运算符,第二个和第三个操作数分别是基本类型和对象。所以,当对象被拆箱时,由于对象为null,拆箱过程中调用null.booleanValue()会报NPE。5.问题解决如果代码这样写,不会报错:Mapmap=newHashMap();Booleanb=(map!=null?map.get("test"):布尔值。假);就是保证三元运算符的第二个和第三个操作数都是对象类型。这样就不会出现自动拆箱的情况,上面代码得到的b的结果为null。PS:本文的例子只是为了让读者更容易理解三元运算符会导致自动拆箱,代码中不一定会直接使用。但是,我自己的代码确实出现了类似的问题。这里简化一下,为了把原理说清楚。【本文为专栏作家霍利斯原创文章,作者微信公众号Hollis(ID:hollishuang)】点此阅读更多本作者好文