zoukankan      html  css  js  c++  java
  • Java三元表达式装箱拆箱NPE问题

    问题

    今天在测试环境的运营后台查询商品库存时发现后端接口报错,返回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
  • 相关阅读:
    linux内核——进程切换宏switch_to
    android源码目录结构详解
    SRM 390(1-250pt)
    SRM 391(1-250pt)
    CodeForces 221(div 2)
    SRM 407(1-250pt, 1-500pt)
    SRM 392(1-250pt)
    SRM 393(1-250pt)
    CodeForces 220(div 2)
    SRM 406(1-250pt, 1-500pt)
  • 原文地址:https://www.cnblogs.com/cdfive2018/p/15434148.html
Copyright © 2011-2022 走看看