专栏名称: 养码场
养码场,一个技术人职场社交平台。 现有“养码人”80000+,覆盖JAVA/PHP/iOS/测试/运维等领域。80%级别在P6及以上,含P9技术大咖30人,技术总监和CTO 500余人。
目录
相关文章推荐
51HR派  ·  给外卖员交社保,困境不止一个 ·  14 小时前  
麒麟出海  ·  万亿市场开放!Wayfair首届亚太峰会来袭 ... ·  14 小时前  
雨果网  ·  亚马逊重大调整!FBA分仓政策新规 ·  2 天前  
传媒招聘那些事儿  ·  网易:高级产品运营(音乐人) ·  3 天前  
51好读  ›  专栏  ›  养码场

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

养码场  · 公众号  ·  · 2020-04-08 16:32

正文

码炸了
前一段时间,项目紧急迭代,临时加入了一个新功能:用户通过浏览器在系统界面上操作,然后Java后台代码做一些数据的查询、计算和整合的工作,并对第三方提供了操作接口。
当晚凌晨上线, 本系统内测试,完美通过!
第二天将接口对外提供,供第三方系统调用, duang! 工单立马来了。
很明显,后台代码炸了!拉了一下后台日志,原来又是烦人的空指针异常 NullPointerException
为此,本文痛定思痛,关于 null 空指针异常问题的预防和解决,详细整理成文,并严格反思: 我们到底在代码中应该如何防止空指针异常所导致的Bug?

最常见的输入判空
对输入判空非常有必要,并且常见,举个栗子:
public String addStudent( Student student ) { // ...}
无论如何,你在进行函数内部业务代码编写之前一定会对传入的 student 对象本身以及每个字段进行判空或校验:
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)
作为一个 “严谨且良心” 的后端开发工程师,如果手动地进行空指针保护,我们难免会这样写:
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 判空 ,因此上面的代码可以重构如下:
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 语法进行改写,比如很常见的一种代码:
List userList = userMapper.queryUserList( userType );if( userList != null ) {//此处免不了对userList进行判空 for( User user : userList ) { // ... // 对user对象进行操作 // ... }}
如果用 Optional 接口进行改造,可以写为:
List userList = userMapper.queryUserList( userType );Optional.ofNullable( userList ).ifPresent( list -> { for( User user : list ) { // ... // 对user对象进行操作 // ... } })
这里的 ifPresent() 的含义很明显:仅在前面的 userList 值不为 null 时,才做下面其余的操作。

只是一颗语法糖
没有用过 Optional 语法的小伙伴们肯定感觉上面的写法 非常甜蜜 !然而褪去华丽的外衣,甜蜜的 Optional 语法底层依然是朴素的语言级写法,比如我们看一下 Optional ifPresent() 函数源码,就是普通的 if 判断而已:
那就有人问: 我们何必多此一举 ,做这样一件无聊的事情呢?
其实不然!
Optional 来包装一个可能为 null 值的变量,其最大意义其实仅仅在于给了调用者一个 明确的警示
怎么理解呢?
比如你写了一个函数,输入学生学号 studentId ,给出学生的得分 :
Score getScore( Long studentId ) { // ...}
调用者在调用你的方法时,一旦忘记 if(score !=null) 判空,那么他的代码肯定是有一定 bug 几率的。
但如果你用 Optional 接口对函数的返回值进行了包裹:
Optional getScore( Long studentId ) { // ...}
这样当调用者调用这个函数时,他可以清清楚楚地看到 getScore() 这个函数的返回值的特殊性(有可能为 null ),这样一个警示一定会很大几率上帮助调用者规避 null 指针异常。

老项目该怎么办?
上面所述的 Optional 语法只是在 JDK 1.8






请到「今天看啥」查看全文