在阿里巴巴的Java开发手册中,提到了在使用三目运算符时必须要注意类型对齐。这是因为在三目运算符中,表达式1和表达式2需要进行类型对齐,而类型对齐的不当可能会导致意想不到的结果或者编译错误。
文档说明
【强制】三目运算符condition? 表达式1 : 表达式2中,高度注意表达式1和2在类型对齐时,可能抛出因自动拆箱导致的NPE异常。 说明:以下两种场景会触发类型对齐的拆箱操作: 1) 表达式1或表达式2的值只要有一个是原始类型。 2) 表达式1或表达式2的值的类型不一致,会强制拆箱升级成表示范围更大的那个类型。 反例: Integer a = 1; Integer b = 2; Integer c = null; Boolean flag = false; // ab的结果是int类型,那么c会强制拆箱成int类型,抛出NPE异常 Integer result=(flag? ab : c);
案例分析
下面我将通过一个代码案例来说明这个问题。
boolean boolean1 = true;
boolean boolean2 = false;
Boolean boolean3 = null;
boolean boolean4 = boolean1 ? boolean3 : boolean2;
System.out.println(boolean4);
以上代码,在运行过程中,会抛出NullPointerException异常
Exception in thread "main" java.lang.NullPointerException
查看反编译代码
为了一探究竟,对以上代码进行反编译,使用jad 工具进行反编译后,得到以下代码
boolean boolean1 = true;
boolean boolean2 = false;
Boolean boolean3 = null;
boolean boolean4 = boolean1 ? boolean3.booleanValue() : boolean2;
System.out.println(boolean4);
可以看到,反编译后的代码,编译器帮我们做了一次自动拆箱,而就是因为这次自动拆箱,导致代码出现对于一个null 对象boolean3.booleanValue()的调用,导致了NullPointerException。
自动装箱与自动拆箱
基本数据类型的自动装箱(autoboxing)、拆箱(unboxing) 是自J2SE 5.0 开始提供的功能。
一般我们要创建一个类的对象的时候,一般使用:
Class a = new Class(parameters);
当我们创建一个Integer 对象时,却可以这样:
Integer i = 100;
实际上,执行上面那句代码的时候,系统为我们执行了:
Integer i = Integer.valueOf(100); // 自动装箱
当我们创建一个int 变量时,是这样创建的:
int j = i;// 自动拆箱
我们可以简单理解为,当我们自己写的代码符合装(拆)箱规范的时候,编译器就会自动帮我们拆(装)箱。
自动装箱都是通过包装类的valueOf() 方法来实现的. 自动拆箱都是通过包装类对象的xxxValue() 来实现的(如booleanValue()、longValue() 等)。
总结自动装箱与自动拆箱,自动装箱就是将基本数据类型自动转换成对应的包装类,自动拆箱就是将包装类自动转换成对应的基本数据类型。
原理分析
通过查看反编译之后的代码,我们准确的定位到了问题,分析之后我们可以得出这样的结论:NullPointerException的原因应该是三目运算符和自动拆箱导致了空指针异常。
根据规定,三目运算符的表达式1 、表达式2操作数的返回值类型应该是一样的,这样才能当把一个三目运算符的结果赋值给一个变量。 如:
Person i = a > b : i1 : i2;
就要求i1 和i2 的类型都必须是Person才行。
因为Java 中存在一种特殊的情况,那就是基本数据类型和包装数据类型,可以通过自动拆装箱的方式互相转换
即可以定义
int i = new Integer(100)
也可以定义
Integer i= 100;
那如果,三目运算符的表达式1 和表达式2 的操作数的类型,分别是基本数据类型和包装类型对象时,就需要有一方需要进行自动拆装箱。
结果就是:由于使用了三目运算符,并且表达式1 、表达式12操作数分别是基本类型和对象。所以对对象进行拆箱操作,由于该对象为null,所以在拆箱过程中调用null.booleanValue() 的时候就报了NullPointerException。
最后总结
总结来说,阿里巴巴的Java开发手册中提到的在三目运算符中,当表达式1和表达式2在类型对齐时,可能会抛出因自动拆箱导致的NullPointerException异常。为了避免这种异常的发生,我们可以使用显式的null检查来避免自动拆箱导致的NullPointerException异常。这样可以提高代码的健壮性和可靠性。