前一段时间,项目紧急迭代,临时加入了一个新功能:用户通过浏览器在系统界面上操作,然后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(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