Java三目运算符导致 NPE

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

在三目运算符中表达式 1 和 2 在涉及算术计算或数据类型转换时会触发自动拆箱。当其中的操作数为 null 值时会导致 NPE 。

一、基础知识

三目运算符

三目运算符是 Java 语言中的重要组成部分它也是唯一有 3 个操作数的运算符。形式为

<表达式1> ? <表达式2> : <表达式3>

以上通过 ?、:  组合的形式得到一个条件表达式。其中 ? 运算符的含义是先求表达式 1 的值如果为真则执行并返回表达式 2 的结果如果表达式 1 的值为假则执行并返回表达式 3 的结果。 

自动装箱与自动拆箱

Java自动拆箱空指针异常

二、问题重现

如下代码在执行是会抛出空指针异常

        Double a = null;
        Integer b = null;
        Double c = Objects.nonNull(b) ? b : a;

异常

三、原理

以上代码反编译出字节码如下

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: aconst_null
         1: astore_1
         2: aconst_null
         3: astore_2
         4: aload_2
         5: invokestatic  #2                  // Method java/util/Objects.nonNull:(Ljava/lang/Object;)Z
         8: ifeq          19
        11: aload_2
        12: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        15: i2d
        16: goto          23
        19: aload_1
        20: invokevirtual #4                  // Method java/lang/Double.doubleValue:()D
        23: invokestatic  #5                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
        26: astore_3
        27: return

  #12行可以看出b Integer类型变量开箱为int值#20行可以看出a Double变量开箱为double值#23行可以看出将根据条件得出的double/int值装箱为Double类型的对象。翻译成java代码如下

 Double c = Double.valueOf(Objects.nonNull(b) ? b.intValue() : a.doubleValue());

ba都是null值因此调用函数时报出npe异常。

分析之后我们可以得出这样的结论三目运算符和自动拆箱导致了空指针异常。

那么为什么编译器会进行自动拆箱呢什么情况下需要进行自动拆箱呢这其实是三目运算符的语法规范。参见jls-15.25摘要如下

 

以去翻阅一下。

其实简单总结下就是

1如果第二个和第三个操作数具有相同的类型(可能是null类型)那么这就是条件表达式的类型。

2如果第二个和第三个操作数中的一个是基本类型T而另一个操作数的类型是对T应用装箱转换(§5.1.7)的结果则条件表达式的类型为T。

3如果第二个和第三个操作数中的一个是空类型而另一个操作数的类型是引用类型则条件表达式的类型就是该引用类型。

否则如果第二个和第三个操作数的类型可转换为数值类型(§5.1.8)则有以下几种情况:

1如果一个操作数是byte或Byte类型另一个是short或Short类型则条件表达式的类型为short。

2如果其中一个操作数是T类型其中T是byte、short或char而另一个操作数是int类型的常量表达式(§15.28)其值可以用T类型表示则条件表达式的类型为T。

3如果其中一个操作数是T类型其中T是字节、短或字符而另一个操作数是int类型的常量表达式(§15.28)其值可以用U类型表示U是对T应用开箱转换的结果那么条件表达式的类型是U。

否则二进制数值提升(§5.6.2)应用于操作数类型条件表达式的类型是第二个和第三个操作数的提升类型。注意二进制数字提升执行值集转换(§5.1.13)并可能执行开箱转换(§5.1.8)。

值集转换规则

当操作符对一对操作数应用二进制数字提升每个操作数必须表示一个可转换为数字类型的值时应用以下规则顺序:

1如果任何操作数是引用类型它将进行开箱转换(§5.1.8)。

2扩展基元转换(§5.1.2)应用于按以下规则指定的任意一个或两个操作数转换:

  • 如果其中一个操作数为double类型则另一个操作数转换为double类型。
  • 否则如果其中一个操作数为float类型则另一个操作数转换为float类型。
  • 否则如果其中一个操作数为long类型则另一个操作数转换为long类型。
  • 否则两个操作数都转换为int类型。

我自己的话总结一下npe其实是值集转换引起的当以下操作符不能自然转换时比如一个值为byte一个值为short可以会产生值集转换而根据值集转换规则1任何引用类型都要开箱那么空值开箱就会导致NPE。

最根的解决方法就是阿里巴巴新出的规范

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java