记忆深刻的一次BUG,久久不能忘怀

2,122 阅读2分钟

三目运算符报NPE了!!!??

第一次遇到的时候,年幼无知的我,觉得明明就是很简单的一行代码,怎么会报错,百思不得其解。

模拟问题代码

public class Test {
    private Long money;

    public Long getMoney() {
        return money;
    }

    public void setMoney(Long money) {
        this.money = money;
    }

    public static void main(String[] args) {
        Test test = new Test();
        Long money = Objects.nonNull(test) ? test.getMoney() : 0L;
        System.out.println(money);
    }
}

输出结果

Exception in thread "main" java.lang.NullPointerException
	at com.gree.invoice.b2c.test.Test.main(Test.java:26)

Process finished with exit code 1

怎么让代码不报错

三种方法返回结果不一样,根据实际场景选择使用。

方法一:使用Long.valueOf(),输出结果为null。

// 输出结果 null
Long money = Objects.nonNull(test) ? test.getMoney() : Long.valueOf(0L);

方法二:对money字段进行判空,输出结果为0。

// 输出结果 0
Long money = Objects.nonNull(test) && Objects.nonNull(test.getMoney()) ? test.getMoney() : 0L;

方法三:Java8还可以使用Optional判空。

// 输出结果 0
Long money = Optional.ofNullable(test).map(Test::getMoney).orElse(0L);
// 输出结果 null
Long money = Optional.ofNullable(test).map(Test::getMoney).orElse(null);

原因解析

参考:《新版阿里巴巴Java开发手册》提到的三目运算符的空指针问题到底是个怎么回事?

Long money = Objects.nonNull(test) ? test.getMoney() : 0L;

因为三目运算符的第二,第三位操作数分别为包装类对象和基本数据类型时,编译器会对包装类自动进行拆箱操作。

在上面的表达式中,0L为基本数据类型longtest.getMoney()为包装类Long,且Long money要求返回值为包装类,所以实际运行的代码为:Long money = Long.valueOf(Objects.nonNull(test) ? test.getMoney().longValue() : 0L);

由实际运行的代码可知,test.getMoney()为null,test.getMoney().longValue()也就是null.longValue()就会导致NPE。

其实,《阿里巴巴Java开发手册(泰山版)》中也有提及:

编码好习惯

那么平时怎么避免这种问题呢?

那就是保持三目运算符的第二位和第三位表达式的类型一致,最后要做好单元测试。

这也就是为什么改成Long money = Objects.nonNull(test) ? test.getMoney() : Long.valueOf(0L);不会再报错的原因了。

有缘下次再见