开发小技巧系列 - 如何避免NullPointerException?(一)

简介: NPE是JAVA界面的常见问题,如何来避免呢?

开发小技巧系列文章,是本人对过往平台系统的设计开发及踩坑的记录与总结,给初入平台系统开发的开发人员提供参考与帮助。

在搬砖的过程中,很多小伙伴遇到最多的问题可能就是 NullPointerException(以下简称NPE)了,大部分的程序代码,都是在组装对象或转换对象上(至少业务系统开发都是这样)。

比如前端的操作界面向后台传入的各种参数,后台接收到参数后,对参数进行简单或者复杂的处理后,构造成另外一个对象,保存到数据库或者传递给下一个处理方法等等。

再比如从后台的数据库中查出某些数据,经过程序的加工处理,转化成另外一个对象,输出到前端的界面上或者输出到其他的方法上等。

这里涉及到java的数据类型:基本类型,包装数据类型,对象。

案例一:

开发的小伙伴应该都知道java有“基本数据类型”,如int, long, float, boolean, short, double等,也知道与其对应的“包装类型”,如Integer, Long, Float, Boolean, Short, Double等。在使用这2种类型时,如果不注意,可能就会为以后埋下“坑”。

来看下面的场景,小伙伴A 在程序中编写了一个方法,用于将类型转变成名称,如下一段代码:ProgramerA.java

    /**
     * 通过输入的类型,来输出男女
     * 程序设定1 - 男, 2-女, 其他-未设置
     */
    public String getGender(int gender){
   
        if(gender == 1){
   
            return "男";
        }else if(gender == 2){
   
            return "女";
        }else {
   
            return "未设置";
        }
    }

这段代码从定义上看,没有什么问题。 另外有个小伙伴B在程序的开发过程中,需要调用到上面的代码,他的编写了如下代码:
ProgramerB.java

/**
     *  模拟小伙伴B调用小伙伴A的方法
     */
    public void getMemberInfo(Integer memberId){
   
        //从数据库中获取某个会员的信息,
        Member member = memberService.getMember(memberId);
        String genderName = ProgrammerA.getGender(member.getGender());
        MemberDTO memberDTO = new MemberDTO()
                .setMemberId(member.getMemberId())
                .setGender(member.getGender())
                .setGenderName(genderName)
                .setNickName(member.getNickName())
                .setRealName(member.getRealName());        return memberDTO;
    }

小伙伴B在写上面代码的前面,写了个单元测试,代码如下:
ProgrammerBTest.java

@Test
    public void getGenderTest(){
   
        //假设不是从数据库中获取数据,而是在程序处理过中,
        Member member = new Member();
        member.setId(1);
        member.setMemberId(1000);
        member.setNickName("测试");
        member.setGender(1);
        log.debug("{}", ProgrammerA.getGender(member.getGender()));
    }

执行这个单元测试后,得到的结果是:

[main] DEBUG net.jhelp.demo.ProgrammerBTest -

小伙伴B通过单元测试的结果,检查了接口没有问题(参数传对),提交代码合并。然而某一天,在测试的过程中产生了异常,从异常的堆栈信息来看是NPE, 小伙伴B看了半天的代码及单元测试都没问题,最后找到组长,通过查找会员的数据记录,发现数据表上的gender字段没有设置默认值,存的值是null,然后通过对代码进行排查,最终解决问题。

那些上面的代码有什么问题呢?先来写一个简单的测试例子:
java复制代码 @Test
public void getGenderWithNull(){
Integer gender = null;
log.debug("{}", ProgrammerA.getGender(gender));
}

运行一下上面的测试用例,发现程序出现了异常,但是从异常的信息上看,又没指示程序是哪里出错了(没有打印具体是那个类的那行代码出问题,只是打印了一个调用的入口),如果没有经验的人,估计很难想到是哪里的问题。而聪明的读者,可能一眼就看到了初始化是null,然后getGender()的入参是int,这里就是“包装类型”向“基本类型”转换的“坑”。

java.lang.NullPointerException
  at net.jhelp.demo.ProgrammerBTest.getGender2(ProgrammerBTest.java:32)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:497)

将小伙伴A写的方法的代码调整如下,在执行上面的测试用例,程序可以正常输出,没有报异常信息,主要调整以下的地方:

  • 1、 入参调整为“Integer”, 使用对象类型;
  • 2、 将"==" 换成 Objects.equals;ProgrammerA.java

    /**
       * 通过输入的类型,来输出男女
       * 程序设定1 - 男, 2-女, 其他-未设置
       */
      public static String getGender2(@NotNull Integer gender){
         
          if(Objects.equals(gender, 1)){
         
              return "男";
          }else if(Objects.equals(gender, 2)){
         
              return "女";
          }else {
         
              return "未设置";
          }
      }
    
      //ProgrammerBTest.java
      /**
       * 测试getGender方法调整后的情况
       */
      @Test
      public void getGenderWithNull2(){
         
          Integer gender = null;
          log.debug("{}", ProgrammerA.getGender2(gender));
      }
    
09:33:42.568 [main] DEBUG net.jhelp.demo.ProgrammerBTest - 未设置

上面的程序只是一个例子,在其它“包装类型”和“基本类型”的转换时,如果不注意,同样也会产生这样的问题。这是因为“包装类型”是对象,对象是允许为NULL,而“基本类型”不是对象(不允许值为NULL),当“包装类型”自动拆包转换成“基本类型”时,如果赋值给“基本类型”时,就会报出NPE的异常。

总结上面问题的解决过程,在开发的过程中,要制定好设计和开发的规范,能更好地保证不埋下“坑”。

数据表的字段设计时,设置好默认值(比如数字类型给出0,字符串为空),而不是NULL(这个对于SQL查询也是有益处的,后续文章来介绍)。

程序中的方式的入参,尽理使用“对象”类型,不要使用“基本类型”,使用对象类型,即使报错,从堆栈信息上,也能看出是那行代码出问题,而不会出现信息指向不明程序中定义变量时,要给出初始值,初始值最好不要使用NULL。

思考一下:

  • 1、 这段程序为什么不用“==”号了? “==” 在数字较大时会有什么问题?
  • 2、 @NotNull 有什么作用?

如果想要上面的代码,可以访问此仓库。
gitee.com/TianXiaoSe_…

在接下来的文章中,会对“对象类型”在程序中怎么去避免NPE,怎么减少 null != obj的判断的场景进行介绍(还会对上面思考进行解答)。

方法(思路)比结论重要,希望你能从中有所收获。


开发小技巧系列文章:

1、开发小技巧系列 - 库存超卖,库存扣成负数?
2、开发小技巧系列 - 重复生成订单
3、开发小技巧系统 - Java实现树形结构的方式有那些?

目录
相关文章
|
6月前
|
Java 程序员 测试技术
我有一个朋友写出了17种触发NPE的代码!避免这些坑
我有一个朋友,写代码的时候常常遭到NPE背刺,痛定思痛,总结了NPE出没的17个场景,哪一个你还没有遇到过?
|
Java 编译器 程序员
【JavaSE专栏67】谈谈异常的那些事,学会预判异常、捕获异常、转移异常
【JavaSE专栏67】谈谈异常的那些事,学会预判异常、捕获异常、转移异常
【JavaSE专栏67】谈谈异常的那些事,学会预判异常、捕获异常、转移异常
|
3月前
|
Java UED 开发者
Java异常处理新玩法:throw关键字,你的错误管理利器!
Java异常处理新玩法:throw关键字,你的错误管理利器!
35 1
|
3月前
|
Java API
JDK版本特性问题之使用 ofNullable 方法来预防 NullPointerException,如何解决
JDK版本特性问题之使用 ofNullable 方法来预防 NullPointerException,如何解决
|
5月前
|
Java UED 开发者
【技术解密】Java异常处理新玩法:throw关键字,你的错误管理利器!
【6月更文挑战第19天】Java异常处理关键在于`throw`,它用于主动抛出异常,确保程序健壮性。例如,当年龄验证失败时,`IllegalArgumentException`被`throw`,提供错误详情。自定义异常如`CustomException`能增强错误信息。此外,通过构建异常链,如在`DataProcessingException`中嵌套`IOException`,保持原始堆栈信息,提供更全面的错误上下文。掌握`throw`能提升错误管理,打造稳定软件。
48 5
|
6月前
|
存储 Java 开发者
探索Java开发中触发空指针异常的场景
作为一名后端开发者在Java编程的世界中,想必大家对空指针并不陌生,空指针异常是一种常见而又令人头疼的问题,它可能会在我们最不经意的时候突然出现,给我们的代码带来困扰,甚至导致系统的不稳定性,而且最可怕的是有时候不能及时定位到它的具体位置。针对这个问题,我们需要深入了解触发空指针异常的代码场景,并寻找有效的方法来识别和处理这些异常情况,而且我觉得空指针异常是每个Java开发者都可能面临的挑战,但只要我们深入了解它的触发场景,并采取适当的预防和处理措施,我们就能够更好地应对这个问题。那么本文就来分享一下实际开发中一些常见的触发空指针异常的代码场景,并分享如何有效地识别和处理这些异常情况。
100 1
探索Java开发中触发空指针异常的场景
项目中常见NPE空指针异常
项目中常见NPE空指针异常
|
6月前
|
安全 Java 程序员
Dating Java8系列之巧用Optional之优雅规避NPE问题
Dating Java8系列之巧用Optional之优雅规避NPE问题
58 0
|
缓存 搜索推荐 Java
开发小技巧系列 - 如何避免NullPointerException?(二)
NPE问题处理之二,引入了optional来处理,还有空对象
85 0
|
Java 测试技术 API
开发小技巧系列 - 如何避免NPE,去掉if...else(四)
利用optional来处理各种IF-ELSE的判断
107 0