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

三元运算符的空指针问题终于被收录进阿里巴巴开发手册

时间:2023-03-12 07:17:43 科技观察

近日,阿里巴巴Java开发手册发布了最新版本泰山版。据说新版本增加了30+条法规。还没来得及仔细看,有个粉丝告诉我,他之前在我的博客里看到过其中一个新规。仔细一看,确实很久以前就遇到过这个问题,也确实在博客中记录过。最早遇到这个问题的是我的同事。他在代码中使用了三元运算符。NPE发生在代码在线运行时。经过排查,发现是三元运算符与自动拆箱之间存在一定的关系导致空指针。本文最初发表于2015年,目前已获得10,000次阅读。趁着最新的开发手册中也提到了这一点,我把之前文章的内容翻出来重新整理了一下,带大家回顾一下这个知识点。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.问题回顾首先根据自己已有的经验看下面的代码:Mapmap=newHashMap();Booleanb=(map!=null?map.get("test"):错误的);上面的代码是我们不注意可能经常写的类代码(很多时候我们爱用三元运算符)。当然,这段代码有问题。执行这段代码时,会报NPE.HashMaphashmap=newHashMap();Booleanboolean1=Boolean.valueOf(hashmap==null?false:((Boolean)hashmap.get("test")).booleanValue());首先要明确的是,既然报了空指针,那肯定是某处调用了空对象的一些方法。在这短短的两行代码中,好像只有一个方法调用了map.get("test"),但是我们都知道map已经提前初始化好了,不会为Null,那么哪里是null呢指针。接下来我们反编译代码。看看我们写的代码经过编译器处理后变成了什么。反编译后的代码如下:HashMaphashmap=newHashMap();Booleanboolean1=Boolean.valueOf(hashmap==null?false:((Boolean)hashmap.get("test")).booleanValue());看完这段反编译后的代码,经过分析,我们大概可以知道问题出在哪里了。((Boolean)hashmap.get("test")).booleanValue()执行过程和结果如下:hashmap.get("test")->null;(Boolean)null->null;null.booleanValue()->报错好了,问题终于定位了。那么让我们看看如何解决这个问题以及它发生的原因。4.原理分析通过查看反编译后的代码,我们准确定位到了问题所在。经过分析,我们可以得出结论,NPE的原因应该是三元运算符和自动拆箱导致了空指针异常。按照规定,三元运算符的第二个和第三个操作数的返回值类型应该相同,这样才能将三元运算符的结果赋值给一个变量。例如:人i=a>b?i1:i2;,要求i1和i2的类型必须是Person。因为Java中有一种特殊情况,就是基本数据类型和封装数据类型可以通过自动拆箱相互转换。也就是说,你可以定义inti=newInteger(10);你也可以定义Integeri=10;那么如果三元运算符的第二个和第三个操作数的类型分别是基本数据类型和封装类型对象,就需要有一方需要自动拆箱。那么具体怎么做呢,根据三元运算符的语法规范。参见jls-15.25,总结如下:如果第二个和第三个操作数的类型相同(可能是null类型),那么就是条件表达式的类型。如果第二个和第三个操作数之一是基本类型T,而另一个的类型是对T应用装箱转换(§5.1.7)的结果,则条件表达式的类型是T。如果其中一个第二个和第三个操作数是空类型,另一个是引用类型,那么条件表达式的类型就是那个引用类型。简单来说:当第二个和第三个操作数分别是原始类型和对象时,对象将被拆箱为基本类型进行操作。所以,结果是:由于使用了三元运算符,第二个和第三个操作数分别是基本类型和对象。所以,当对象被拆箱时,由于对象为null,拆箱过程中调用null.booleanValue()会报NPE。5.问题解决如果代码这样写,不会报错:Mapmap=newHashMap();Booleanb=(map!=null?map.get("test"):布尔值。假);就是保证三元运算符的第二个和第三个操作数都是对象类型。这与三元运算符有关。作者简介:Hollis,对Coding有着独特追求的人,目前是阿里巴巴技术专家,个人技术博主,技术文章全网阅读数千万。他也是《程序员的三门课》的合著者。【本文为专栏作家霍利斯原创文章,作者微信公众号Hollis(ID:hollishuang)】点此阅读更多本作者好文