一个三目运算符引发的“血案”

问题描述

今天有个同事发过来一个消息,说他的系统今天老是报错误,代码看了好几次都没发现问题,百思不得其解,让我帮忙看眼。我拿到代码后,乍一看也没看出个所以然,但是在执行单元测试之后,问题很快就暴露出来了,坑啊~~~~ 大致代码如下:

        class Operator{
            Long cityIdCharge;
            .....
        }

        ......
        
        Operator operator = new Operator();
        Long currentCityId = operator == null ? 0L : operator.getCityInCharge();
        System.out.println(currentCityId);

在执行如上代码时,将会抛出NPE异常,而且错误位于代码第二行。那为什么执行第二行代码会出现NPE错误呢,下面我们来分析一下。

原因分析

根据异常堆栈,可以看到,异常定位于第二行代码。第二行代码是一个三元表达式,我们知道三目运算符只要后面两个是不同的类型,涉及到类型转换,那么编译器就会向下(基本类型)进行转型,再进行计算。换言之,如果在计算表达式中,有Integer和int,那么Integer类型会先转化为int再进行计算。
在开始分析代码之前,需要回顾一下java的基本数据类型。long是8种基本数据类型之一,Long是long的包装类,继承于Number类。
在示例代码中,operator.getCityInCharge()返回的是Long类型,值为null。三元表达式的第二个值是0L,我们知道,Long的默认值是null.long的默认值是0L,因此,对于上述示例代码中的第二行代码,Long和long不是同一个类型,编译器会默认向下转型,Long转为long,但是此时Long类型对应的值为null,因此抛出NPE。

Long与long之间的相互转化

  1. 拆箱操作
    可以使用longValue()方法将Long变为long
 Long a = 1L;
 long b = a.longValue();

也可以直接进行拆箱操作

long c = a;

  1. 装箱操作
    可以使用new方法将long变成Long

long a = 1L;
Long b = new Long(a);
也可以直接进行装箱操作
Long c = a;

因此上述的示例代码可以修改为

Long currentCityId = operator == null ? new Long(0) : operator.getCityInCharge();

或者使用Optional结构对operator.getCityInCharge()进行封装后再进行计算

Optional.ofNullable(operator.getCityInCharge()).orElse(0L)

写在最后

  1. 在使用对象时,需要先验空,再进行操作处理,也可以使用guava中的Optional对对象进行封装
  2. 使用三元运算符时最后使用相同类型的对象
  3. 一般来说,属性的值建议使用封装类型,临时变量建议使用基本类型

你可能感兴趣的:(一个三目运算符引发的“血案”)