前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >只因少写一个判空,我的代码上线后炸了!

只因少写一个判空,我的代码上线后炸了!

作者头像
养码场
发布2020-04-10 13:41:35
9260
发布2020-04-10 13:41:35
举报
文章被收录于专栏:养码场

代码炸了

前一段时间,项目紧急迭代,临时加入了一个新功能:用户通过浏览器在系统界面上操作,然后Java后台代码做一些数据的查询、计算和整合的工作,并对第三方提供了操作接口。

当晚凌晨上线,本系统内测试,完美通过!

第二天将接口对外提供,供第三方系统调用,duang!工单立马来了。

很明显,后台代码炸了!拉了一下后台日志,原来又是烦人的空指针异常NullPointerException

为此,本文痛定思痛,关于 null空指针异常问题的预防和解决,详细整理成文,并严格反思:我们到底在代码中应该如何防止空指针异常所导致的Bug?

最常见的输入判空

对输入判空非常有必要,并且常见,举个栗子:

代码语言:javascript
复制
public String addStudent( Student student ) {
   // ...
}

无论如何,你在进行函数内部业务代码编写之前一定会对传入的 student对象本身以及每个字段进行判空或校验:

代码语言:javascript
复制
public String addStudent( Student student ) {

    if( student == null )
        return "传入的Student对象为null,请传值";

    if( student.getName()==null || "".equals(student.getName()) )
        return "传入的学生姓名为空,请传值";
    if( student.getScore()==null )
        return "传入的学生成绩为null,请传值";
    if( (student.getScore()<0) || (student.getScore()>100) )
        return "传入的学生成绩有误,分数应该在0~100之间";
    if( student.getMobile()==null || "".equals(student.getMobile()) )
        return "传入的学生电话号码为空,请传值";
    if( student.getMobile().length()!=11 )
        return "传入的学生电话号码长度有误,应为11位";

    studentService.addStudent( student ); // 将student对象存入MySQL数据库
    return "SUCCESS";
}

手动空指针保护

手动进行 if(obj !=null)的判空自然是最全能的,也是最可靠的,但是怕就怕俄罗斯套娃式if判空。

举例一种情况:

为了获取:省(Province)→市(Ctiy)→区(District)→街道(Street)→道路名(Name)

作为一个“严谨且良心”的后端开发工程师,如果手动地进行空指针保护,我们难免会这样写:

代码语言:javascript
复制
public String getStreetName( Province province ) {
    if( province != null ) {
        City city = province.getCity();
        if( city != null ) {
            District district = city.getDistrict();
            if( district != null ) {
                Street street = district.getStreet();
                if( street != null ) {
                    return street.getName();
                }
            }
        }
    }
    return "未找到该道路名";
}

为了获取到链条最终端的目的值,直接链式取值必定有问题,因为中间只要某一个环节的对象为 null,则代码一定会炸,并且抛出 NullPointerException异常,然而俄罗斯套娃式的 if判空实在有点心累。

消除俄罗斯套娃式判空

Optional接口本质是个容器,你可以将你可能为 null的变量交由它进行托管,这样我们就不用显式对原变量进行 null值检测,防止出现各种空指针异常。

Optional语法专治上面的俄罗斯套娃式 if 判空,因此上面的代码可以重构如下:

代码语言:javascript
复制
public String getStreetName( Province province ) {
    return Optional.ofNullable( province )
            .map( i -> i.getCity() )
            .map( i -> i.getDistrict() )
            .map( i -> i.getStreet() )
            .map( i -> i.getName() )
            .orElse( "未找到该道路名" );
}

漂亮!嵌套的 if/else判空灰飞烟灭!

解释一下执行过程:

  • ofNullable(province ) :它以一种智能包装的方式来构造一个 Optional实例, province是否为 null均可以。如果为 null,返回一个单例空 Optional对象;如果非 null,则返回一个 Optional包装对象
  • map(xxx ):该函数主要做值的转换,如果上一步的值非 null,则调用括号里的具体方法进行值的转化;反之则直接返回上一步中的单例 Optional包装对象
  • orElse(xxx ):很好理解,在上面某一个步骤的值转换终止时进行调用,给出一个最终的默认值

当然实际代码中倒很少有这种极端情况,不过普通的 if(obj !=null)判空也可以用 Optional语法进行改写,比如很常见的一种代码:

代码语言:javascript
复制
List<User> userList = userMapper.queryUserList( userType );
if( userList != null ) {//此处免不了对userList进行判空
  for( User user : userList ) {
    // ...
    // 对user对象进行操作
    // ...
  }
}

如果用 Optional接口进行改造,可以写为:

代码语言:javascript
复制
List<User> userList = userMapper.queryUserList( userType );
Optional.ofNullable( userList ).ifPresent(
  list -> {
    for( User user : list ) {
      // ...
      // 对user对象进行操作
      // ...
    }
  }
)

这里的 ifPresent()的含义很明显:仅在前面的 userList值不为 null时,才做下面其余的操作。

只是一颗语法糖

没有用过 Optional语法的小伙伴们肯定感觉上面的写法非常甜蜜!然而褪去华丽的外衣,甜蜜的 Optional语法底层依然是朴素的语言级写法,比如我们看一下 OptionalifPresent()函数源码,就是普通的 if判断而已:

那就有人问:我们何必多此一举,做这样一件无聊的事情呢?

其实不然!

Optional来包装一个可能为 null值的变量,其最大意义其实仅仅在于给了调用者一个明确的警示

怎么理解呢?

比如你写了一个函数,输入学生学号 studentId,给出学生的得分 :

代码语言:javascript
复制
Score getScore( Long studentId ) {
  // ...
}

调用者在调用你的方法时,一旦忘记 if(score !=null)判空,那么他的代码肯定是有一定 bug几率的。

但如果你用 Optional接口对函数的返回值进行了包裹:

代码语言:javascript
复制
Optional<Score> getScore( Long studentId ) {
  // ...
}

这样当调用者调用这个函数时,他可以清清楚楚地看到 getScore()这个函数的返回值的特殊性(有可能为 null),这样一个警示一定会很大几率上帮助调用者规避 null指针异常。

老项目该怎么办?

上面所述的 Optional语法只是在 JDK 1.8版本后才开始引入,那还在用 JDK 1.8版本之前的老项目怎么办呢?

没关系!

Google大名鼎鼎的 Guava库中早就提供了 Optional接口来帮助优雅地处理 null对象问题,其本质也是在可能为 null的对象上做了一层封装,使用起来和JDK本身提供的 Optional接口没有太大区别。

你只需要在你的项目里引入 GoogleGuava库:

代码语言:javascript
复制
<dependency>
 
<groupId>com.google.guava</groupId>
 
<artifactId>guava</artifactId>
 
</dependency>
 

即可享受到和 Java8版本开始提供的 Optional一样的待遇!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 养码场 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档