问题概述
同学们好呀,我是老骥!
跟往常一样,为了代码的优雅就不写太多的 if…else…,于是一顿噼里啪啦的操作,写了一个三元表达式来获取值,一运行竟然报错了。代码如下:
public static void main(String[] args) {
Integer a = null;
boolean flag = false;
Integer b = flag ? 1 : a;
}
代码很简单,就是一个普通的三元表达式,按照之前的预期,当 flag 为 true 时,自动装箱 1 赋值给 b;当 flag 为 flase 时,返回了 null 给 b。但是,运行代码之后就报了空指针异常: Exception in thread "main" java.lang.NullPointerException。可真行,写了多年的三元表达式了,头一次遇到这种情况。
问题分析
那为什么会出现空指针这种情况呢?
我们赋值给 Integer 对象一个 1 或 null 都可以直接运行成功,且直接使用 Object 对象来接收三元表达式的返回值也报了同样的空指针异常,那么问题就出现在了三元表达式上的数据类型转换上了。
三元表达式数据类型转换有以下规则:
当两个操作数不可互相转换时,则不转换,直接返回 Object 的类型。
如
Object b = flag ? new String() : new Integer(1024);中 String 和 Integer是两种不同类型的对象,不可以互相转换,就直接返回了 Object 类型的数值。若存在两个操作数:S、T,S 包含 T,则最终返回 S 类型的值。
代码示例:
public static void main(String[] args) { Integer a = 1024; boolean flag = true; Object b1 = flag ? 1 : a; Object b2 = flag ? a : 1; System.out.println("b1: " + b1 + ", b1 instanceof Integer: " + (b1 instanceof Integer)); System.out.println("b2: " + b2 + ", b2 instanceof Long: " + (b2 instanceof Integer)); }运行结果:
b1: 1, b1 instanceof Integer: true b2: 1024, b2 instanceof Long: true从上述运行结果可以看到,最终返回的 Object 类型都是 Integer 类型,因为 Integer 包含 null 值和 int 类型的值。
如果两个操作数能够互相转换,则向上转型。
代码示例:
public static void main(String[] args) { Integer a = 1024; boolean flag = true; Object b1 = flag ? 1L : a; Object b2 = flag ? a : 1L; System.out.println("b1: " + b1 + ", b1 instanceof Long: " + (b1 instanceof Long)); System.out.println("b1: " + b1 + ", b1 instanceof Long: " + (b1 instanceof Integer)); System.out.println("b2: " + b2 + ", b2 instanceof Long: " + (b2 instanceof Long)); System.out.println("b2: " + b2 + ", b2 instanceof Long: " + (b2 instanceof Integer)); }运行结果:
b1: 1, b1 instanceof Long: true b1: 1, b1 instanceof Long: false b2: 1024, b2 instanceof Long: true b2: 1024, b2 instanceof Long: false从上述运行结果可以看到,三元表达式返回的 Object 对象都是 Long 类型,即使表达式运算结果返回了值为 Integer 的对象,最终也经过拆箱装箱操作,向上转型为了 Long 类型。
了解了三元表达式的规则,再回过头来看一下一开始报空指针异常的代码:
public static void main(String[] args) {
Integer a = null;
boolean flag = false;
Integer b = flag ? 1 : a;
}
按照上述表达式中的操作数类型,应该使用了第 2 条匹配规则,即返回了范围类型大的 Integer 类型。
我们使用 javap -c Test.class 反编译一下字节码文件。
可以看到,当 a 赋值给 b 的时候,是执行了 a 对象的 Integer.intValue() 先拆箱再进行装箱 Integer.valueOf() 返回一个新的 Integer 对象给 b。所以,当 Integer 类型的对象 a 为 null 时,肯定是无法执行该对象下的 intValue() 方法的,所以也就抛出了空指针异常。
Integer.java 部分源码:
public final class Integer extends Number implements Comparable<Integer> {
private final int value;
public int intValue() {
return value;
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
}
那解决上述问题的方法也很简单,只需要保证两个操作数的类型一致即可,代码如下:
public static void main(String[] args) {
Integer a = null;
boolean flag = false;
Integer b = flag ? new Integer(1) : a;
System.out.println("b1: " + b + ", b1 instanceof Integer: " + (b instanceof Integer));
}
运行结果:
b1: null, b1 instanceof Integer: false
总结
这次在三元表达式上踩的坑,应该很多人都曾经遇到过。这种不算是语法错误,所以编译器无法捕获到这种异常,而且在运行阶段非 null 的情况下也无法暴露出来,所以在我们在写三元表达式的时候要谨慎一点。
要避免这种情况,还是要打好自己的基础,熟悉三元表达式的数据转换规则。在实现过程中尽量使用同种类型的操作数,同时本地做好各种数据类型的空值测试。