近期碰到一个比较常见的错误: 空指针! Exception in thread “main” java.lang.NullPointerException,虽然有些空指针问题是很容易可以避免的,比如在eclipse上装一个Find Bugs插件就可以检测空指针。但有些空指针是插件检测不出来的,同时对于初学的java开发者来说,仅凭肉眼是很难看出来的。比如像下面的这段代码:
public class Test {public static void main(String args[]) { House house = new House();int doorNum = house != null ? house.getDoorNum() : 0; } } class House { Integer doorNum;public Integer getDoorNum() {return doorNum; } }
这里用到的三目运算符是在java代码中很常见的,可以算是对if-else的一种简化写法。
因此把这句代码可以翻译成这样子:
int doorNum;if (house != null) { doorNum = house.getDoorNum(); } else { doorNum = 0; }
这里需要先大概介绍拆箱和装箱的概念:
装箱:把基本类型用它们相应的引用类型包装起来,使其具有对象的性质。int包装成Integer、float包装成Float
拆箱:和装箱相反,将引用类型的对象简化成值类型的数据
这样一看错误就比较明显了,因为当执行doorNum = house.getDoorNum();这句代码时,编译器执行了自动拆箱,所以事实上虚拟机执行的是doorNum = house.getDoorNum().intValue();因此,当house.getDoorNum()为null时,就会报空指针错误了。所以如果想要把house.getDoorNum()的值付给doorNum,必须同时判断house.getDoorNum()是否为空,那么就需要两个判空的逻辑,似乎太麻烦。于是我想到了把doorNum进行封装,变成Integer类型,那么当执行doorNum = house.getDoorNum();这句代码时,不就不用自动拆箱了嘛?问题不就解决了嘛?因为这样子如果翻译成if-else不就等于这样了嘛:
Integer doorNum;if (house != null) { doorNum = house.getDoorNum(); } else { doorNum = 0; }
这样看起来没有什么问题了啊,但是不幸的是,空指针错误依旧存在。
于是再去看反编译文件,此时反编译后的代码已经变成了如下:
Integer doorNum = Integer.valueOf(house != null ? house.getDoorNum().intValue() : 0);
看了反编译文件,知道原来是我想当然了,三目运算符虽然很像if-else,但不完全一样。虽然把doorNum进行了封装,变成Integer类型。但是自动拆箱的步骤还是在,虚拟机是先执行了拆箱,接着又进行了自动装箱。那么为什么会这样呢?会不会和这个0有关呢?于是我把0也封装起来:
Integer doorNum = house != null ? house.getDoorNum() : new Integer(0);
果然问题解决了。此时三目运算的第二、三位全是封装类型,没有基本数据类型,所以没有拆箱和装箱了。但是当第二、三位中有一个是基本类型而另一个是封装类型时,就会执行自动拆箱。
三目运算符的语法定义如下:
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.
通过这个例子,大概总结出了以下几点,有不对的地方请指正:
1:这里只是举了Integer和int的例子,Long/long、Boolean/boolean等等也是同样的;
2:JAVA自动拆/装箱的确给开发者提供了许多便利,但是实际使用起来,还是有许多地方需要注意的;
3:虽然三目运算符看上去很简约,但是还是得注意正确使用,它不全等于if-else;
4:当三目运算符中的第二、三位中,有一个是基本数据类型并且另一个是封装类型时,编译器就进行会自动拆箱;
5:有时候研究半天看似怎么都没有错的代码还不如看看反编译后的代码。
转载于:https://my.oschina.net/u/2250363/blog/666855