Java三元表达式装箱拆箱NPE问题

摘要:
问题今天,当在测试环境的操作后台查询商品库存时,我们发现后端接口报告了一个错误。返回代码为904,表示内部错误。仔细分析这行代码。它使用三元表达式来判断。执行表达式后,它直接返回,而方法返回Integer类型,xxxRoGetQuantity()也是Integer类型(这似乎没有问题)。注意,三元表达式的另一个分支返回0。它提醒我,在装箱/解包时,Java可能有NPE。刚才,通过IDEA中的断点,我看到xxxRo的数量字段为空。因为返回值为0,所以可能是先执行解包,然后转换装箱。

问题

今天在测试环境的运营后台查询商品库存时发现后端接口报错,返回code为904,该错误码表示内部错误。于是在微服务日志里查看,发现某方法报了NPE(java.lang.NullPointer)。
方法里关键的报错代码如下:

public Integer queryXxx(String xx, String yy) {
  ...
  XxxRo xxxRo = queryXxxRo(xx, yy);
  return xxxRo != null ? xxxRo.getQuantity() : 0;
}

日志里错误异常堆栈里看到抛NPE异常的行号对应这一行代码:
return xxxRo != null ? xxxRo.getQuantity() : 0;

初探

初一看如果变量xxxRo为null,那么xxxRo.getQuantity()会抛NPE
可语句里判断了的xxxRo不为null才执行,否则返回0,按理说变量xxxRo为null应返回0。

queryXxxRo(xx, yy)是从Redis里查询数据,将相关参数拼好key在Redis去查发现有数据。
Redis里存储类型为hash,对应XxxRo里的每个字段,其中hget xxx quantity值为4000000012。

复现

本地启动库存服务,通过dubbo支持的telnet里invoke命令调用该接口,也是那一行代码抛NPE
我在return xxxRo != null ? xxxRo.getQuantity() : 0;这一行代码打了个断点调试,
发现xxxRo不为null,在IDEA里展开该对象,其中各字段都有值,只有quantity字段为null。

quantity在Redis查出来值为4000000012,为何xxxRo里的quantity字段为null?
注意到日志里还有一个异常:
java.lang.NumberFormatException: For input string: "4000000012"
这里因为4000000012超过了Integer.MAX的值2147483647,项目框架里Redis的hash转换为Ro对象时用的Integer.valueOf()
该方法抛的NumberFormatException,转换单个字段失败后记录了错误日志并继续执行。
4000000012是其它系统推过来的值,经检查日志和沟通,是测试同学另外一个系统的界面上设置的值过大。

通过Fn + Option + F8调出Evalute窗口,将xxxRo != null ? xxxRo.getQuantity() : 0复制进去执行,结果为null,并没有抛NPE
F9放开断点进行执行,日志里打印NPE,跟测试环境一致。

分析

仔细审视这行代码,它用到了三元表达式来判断,表达式执行后直接return,而方法的返回是Integer类型,
xxxRo.getQuantity()也是Integer类型,好像没问题。

注意到三元表达式的另一个分支返回的是0,想起Java在装箱/拆箱时(boxing/unboxing)可能会有NPE,
刚才本地复现时通过断点在IDEA的看到xxxRo的quantity字段为null,因为返回值是0,
可能这里先进行了拆箱,然后进行装箱的转换。

模拟

int b = (Integer) null;
这行代码null经过装箱,然后自动拆箱时抛了NPE

继续通过几个例子来模拟:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Optional;

/**
 * @author cdfive
 */
public class SpecialNPETest {

    public static void main(String[] args) {
        // Basic test
        basic();

        // OK
        case1();

        // NPE
        case2();

        // NPE
        case3();

        // OK
        case4();

        // OK
        case5();

        // OK
        case6();
    }

    private static void basic() {
        System.out.println("basic start");
        // OK
        Integer a = (Integer) null;
        // NPE
        try {
            int b = (Integer) null;
        } catch (Exception e) {
            System.out.println("exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
        }
        System.out.println();
        System.out.println("basic end");
    }

    private static void case1() {
        System.out.println("case1 start");
        try {
            Item item = new Item();
            item.setId(1);
            item.setQuantity(5);

            // OK
            Integer result = item != null ? item.getQuantity() : 0;
            System.out.println(result);
        } catch (Exception e) {
            System.out.println("case1 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
        }
        System.out.println("case1 end");
        System.out.println();
    }

    private static void case2() {
        System.out.println("case2 start");
        try {
            Item item = new Item();
            item.setId(1);

            // NPE
            Integer result = item != null ? item.getQuantity() : 0;
        } catch (Exception e) {
            System.out.println("case2 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
        }
        System.out.println("case2 end");
        System.out.println();
    }

    private static void case3() {
        System.out.println("case3 start");
        try {
            Item item = new Item();
            item.setId(1);

            // NPE
            int result = item != null ? item.getQuantity() : 0;
            System.out.println(result);
        } catch (Exception e) {
            System.out.println("case3 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
        }
        System.out.println("case3 end");
        System.out.println();
    }

    private static void case4() {
        System.out.println("case4 start");
        try {
            Item item = new Item();
            item.setId(1);

            // OK
            Integer result;
            if (item != null) {
                result = item.getQuantity();
            } else {
                result = 0;
            }
            System.out.println(result);
        } catch (Exception e) {
            System.out.println("case4 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
        }
        System.out.println("case4 end");
        System.out.println();
    }

    private static void case5() {
        System.out.println("case5 start");
        try {
            Item item = new Item();
            item.setId(1);

            // OK
            Integer result = item != null ? item.getQuantity() : Integer.valueOf(0);
            System.out.println(result);
        } catch (Exception e) {
            System.out.println("case5 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
        }
        System.out.println("case5 end");
        System.out.println();
    }

    private static void case6() {
        System.out.println("case6 start");
        try {
            Item item = new Item();
            item.setId(1);

            // OK
            Integer result = Optional.ofNullable(item).map(o -> o.getQuantity()).orElse(null);
            System.out.println(result);
        } catch (Exception e) {
            System.out.println("case6 error,exception=" + e.getClass().getName() + ",msg=" + e.getMessage());
        }
        System.out.println("case6 end");
        System.out.println();
    }

    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    private static class Item implements Serializable {

        private Integer id;

        private Integer quantity;
    }
}

解决

return xxxRo != null ? xxxRo.getQuantity() : 0;修改这行代码。

3种思路:

  1. 改用if/else判断
Integer result;
if (xxxRo != null) {
    result = xxxRo.getQuantity();
} else {
    result = 0;
}
  1. 基础类型0改为包装类型Integer.value(0)
Integer result = xxxRo != null ? xxxRo.getQuantity() : Integer.valueOf(0);
  1. 改用Optional处理
Integer result = Optional.ofNullable(xxxRo).map(o -> o.getQuantity()).orElse(0);

注意:

  • 当xxxRo不为null,xxxRo里的quantity为null时,前2种方式返回的是null,第3种方式里返回的是0
  • 当xxxRo为null时,3种方式都返回0

免责声明:文章转载自《Java三元表达式装箱拆箱NPE问题》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇wifi的几种工作模式MySQL查询性能优化下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

Mock 基本使用

特殊的格式,例如IP,随机数,图片,地址,需要去收集二、mock优点1、前后端分离让前端工程师独立于后端进行开发。表示需要拦截的Ajax请求类型。表示数据模板,可以是对象或字符串。表示用于生成响应数据的函数。...

docker运行mysql容器自动停止的问题解救方案如下

3.此时,通过输入dockers可以发现MySQL容器正在正常运行:...

使用jsPlumb插件实现动态连线功能

jsPlumb是一个强大的JavaScript连线库,它可以将html中的元素用箭头、曲线、直线等连接起来,适用于开发Web上的图表、建模工具等,其实jsPlumb可能主要是用来做流程图的,它在实现这方面的功能上非常强大,我在项目中只使用了它少部分功能,来实现项目中连线的效果。...

PartⅠ邮件伪造

什么是伪造发件人邮件地址简单邮件传输协议 (Simple Mail Transfer Protocol, SMTP) 即简单邮件传输协议,是在Internet传输email的事实标准。正如名字所暗示的那样,它其实是一个非常简单的传输协议,无需身份认证,而且发件人的邮箱地址是可以由发信方任意声明的,利用这个特性可以伪造任意发件人。如何识别虚假(欺骗性)电子邮件...

db2字符串函数

可以指定可选的字符串长度单位,以指示哪些单位表示函数的起始位置和结果。使用基于字符的函数解决了将字节位置返回到字符位置的问题。代码单元16和代码单元32根据字符数计数。类似地,CODEUNITS32指定使用Unicode UTF-32来理解多字节字符的字符边界。如果使用CODEUNITS获取字符长度,则用作字符串函数输入的不同CODEUNITS将导致不同的输...

如何在Java应用中提交Spark任务?

我丈夫是一个用户定义的ID,作为参数传递给Spark应用程序;Spark初始化后,可以通过SparkContext_ ID和URL通过驱动程序连接到数据库,新版本关联关系的插入归因于互联网时代的信息爆炸。我看到了群友的聊天,了解了SparkLauncher。经过调查,我发现它可以基于Java代码自动提交Spark任务。因为SparkLauncher的类引用了...