那既然处理起来简单,有什么好纠结的呢?老老实实校验不就完了,但整个处理的过程中对程序员来说体验是非常糟糕的;
- 让代码冗长很多时候,核心的业务逻辑代码量是不大的,但是一旦加上各种判断、校验,就会让代码变的冗长,可读性、维护性随之下降;
- 纯苦力活像这种机械式的判空、校验本质上就是一些体力活,没有任何编码乐趣可言,长时间编写这种代码,会丧失对编程的激情;
- 易背锅很多业务需要多人合作,有时候可能会出现侥幸心里,都认为其他人在用的时候会处理;无形中挖了些坑,一不小心就锅从天降;
基于上面这些不太好的体验,让消除的难度增加了不少;
有时候当需求很着急的时候,程序员大部分都会选择以功能为主,一些不太重要的东西总是想着晚点再来补充,先跳过写重要的内容,结果是一跳过就没有然后了;
为了既能解决NPE问题,又不影响我们的开发效率; JDK 、三方框架为我们提供了很多优秀的工具类,大可不必自己耗时耗力去再造轮子了;
下面就通过10个妙招,来彻底解决NPE问题:
1Objects 工具类
既然要解决空指针,自然就是提前对对象进行判空校验;通常情况下,会使用 if ( null != obj ) 进行对象校验;在 Java 7 中,专门提供工具类 java.util.Objects ,让对象的判空校验更加简单;
特点
- Java 7 自带,不需要额外的依赖
- 静态方法 ,使用简单
- 仅支持对象判空
示例
- Objects.isNull
判断对象是否为空,为 null 返回 true ,否则返回 false
Object obj = null;
System.out.println(Objects.isNull(obj)); // true
obj = new Object();
System.out.println(Objects.isNull(obj)); // false
- Objects.nonNull
和 Objects.isNull 相反;判断对象不为空,为 null 返回 false ,否则返回 true
Object obj = null;
System.out.println(Objects.nonNull(obj)); // false
obj = new Object();
System.out.println(Objects.nonNull(obj)); // true
- Objects.requireNonNull
校验非空,一旦对象为空,就会抛出空指针异常(NullPointerException),改方法可以自定义异常描述,方便异常之后能快速定位问题所在:
Object obj = null;
Objects.requireNonNull(obj);
// 自定义错误描述
Objects.requireNonNull(obj,"obj 对象为空");
执行输出:
Exception in thread "main" java.lang.NullPointerException: obj 对象为空
at java.util.Objects.requireNonNull(Objects.java:)
at com.ehang.helloworld.controller.NullTest.t(NullTest.java:97)
at com.ehang.helloworld.controller.NullTest.main(NullTest.java:)
2字符串判空
字符串 是开发过程中使用最多一种数据类型,因此对字符串的判断、校验也就必不可少了,原生的方式都是通过空对象,长度进行判断:
String str = "一行Java"
if ( null != str && s.length() > 0 ){
// 对str字符串进行使用
}
但是,对字符串的校验,除了判空之外,还有很多其他的场景,比如判断是不是空串(String str = “” ),是不是只有空格(String str = ” ” )等等,那这些校验,就会麻烦一些了;不过木有关系,现成的工具类已经足够满足了;
Spring StringUtil工具类
org.springframework.util.StringUtils 是String 框架自带的字符串工具类,功能比较单一,在教新的版本中,这个工具类的字符串判空方法已经被弃用了,所以不太建议使用了;
- String Utils.isEmpty
空对象以及空串的校验;
String s = null;
String s = "";
String s = " ";
System.out.println(StringUtils.isEmpty(s)); // true
System.out.println(StringUtils.isEmpty(s)); // true
System.out.println(StringUtils.isEmpty(s)); // false
apache lang3 StringUtil工具类
apache lang3 StringUtil 工具类( org. apache .commons.lang3.StringUtils ) 相比于Spring 框架带的工具类,要强大太对了,涵盖了对String 操作的所有封装;
判空校验的话主要有4个 StringUtils.isEmpty 、 StringUtils.isNotEmpty 、 StringUtils.isBlank 、 StringUtils.isNotBlank
- 依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
- StringUtils.isEmpty 和 StringUtils.isNotEmpty
判断字符串对象是否为空,以及字符串长度是否为0; isEmpty 和 isNotEmpty 校验结果相反;
String s = null;
String s = "";
String s = " ";
System.out.println(StringUtils.isEmpty(s)); // true
System.out.println(StringUtils.isEmpty(s)); // true
System.out.println(StringUtils.isEmpty(s)); // false
System.out.println();
System.out.println(StringUtils.isNotEmpty(s)); // false
System.out.println(StringUtils.isNotEmpty(s)); // false
System.out.println(StringUtils.isNotEmpty(s)); // true
- StringUtils.isBlank 、 StringUtils.isNotBlank 在 StringUtils.isEmpty 和 StringUtils.isNotEmpty 判断的基础上,还会将字符串开头,结尾的空格去掉之后,判断长度是否大于0
String s = null;
String s = "";
String s = " ";
String s = " 1 2 ";
System.out.println(StringUtils.isBlank(s)); // true 空对象
System.out.println(StringUtils.isBlank(s)); // true 长度等于0
System.out.println(StringUtils.isBlank(s)); // true 去掉前后空格之后,长度也等于0
System.out.println(StringUtils.isBlank(s)); // false 去掉前后空格(1 2),长度大于0
System.out.println();
System.out.println(StringUtils.isNotBlank(s)); // false
System.out.println(StringUtils.isNotBlank(s)); // false
System.out.println(StringUtils.isNotBlank(s)); // false
System.out.println(StringUtils.isNotBlank(s)); // true
- 其他功能
本文主要是探讨判空校验,lang3 的 StringUtil 工具类几乎涵盖了所有关于String操作的封装,大大降低了我们处理 String 的复杂度,更多功能可参考官方文档
3字符串比较
在对字符串进行比较的时候,也需要特别注意NPE异常;
如下示例:
public Boolean isEhang(String name) {
if (name.equals("ehang")) {
return true;
}
return false;
}
当如果name为null的时候,就会出现NPE异常;
可以做如下调整:
if ("ehang".equals(name))
...
这样就算name为null,即不会出现NPE异常,也能正常的判断;
4Map、List、Set 判空
Map 、List、Set 是经常会用到的数据结构,虽然他们都包含有 isEmpty() 方法,能判断容器中是否包含了元素,但是无法判断自生对象是否为空,一旦对象没有实例化时,调用isEmpty()就会报空指针异常;Spring 为我们提供了一个 org.springframework.util.CollectionUtils 工具类,其中的 isEmpty 就会优先判断对象是否为空,然后再通过isEmpty()判断是否存在元素,能大大减少因为对象为空带来的空指针异常;
Map map = null;
System.out.println(map.isEmpty()); // 空指针异常
System.out.println(CollectionUtils.isEmpty(map)); // true
map = new HashMap ();
System.out.println(map.isEmpty()); // true
System.out.println(CollectionUtils.isEmpty(map)); // true
map.put("", "2");
System.out.println(CollectionUtils.isEmpty(map)); // false
System.out.println(map.isEmpty()); // false
List list = null;
System.out.println(list.isEmpty()); // 空指针异常
System.out.println(CollectionUtils.isEmpty(list)); // true
list = new ArrayList();
System.out.println(list.isEmpty()); // true
System.out.println(CollectionUtils.isEmpty(list)); // true
list.add("");
System.out.println(CollectionUtils.isEmpty(list)); // false
System.out.println(list.isEmpty()); // false
Set set = null;
System.out.println(set.isEmpty()); // 空指针异常
System.out.println(CollectionUtils.isEmpty(set)); // true
set = new TreeSet();
System.out.println(set.isEmpty()); // true
System.out.println(CollectionUtils.isEmpty(set)); // true
set.add("");
System.out.println(CollectionUtils.isEmpty(set)); // false
System.out.println(set.isEmpty()); // false
除了判空之外,该工具类还包含了很多很实用的方法,比如获取第一个元素:firstElement() 、最后一个元素:lastElement()、是否包含某个元素:contains() 等等
hutool的CollectionUtil
单纯判空,前面Spring的CollectionUtils已经足够,其他的功能也够满足绝大部分的使用场景; hutool的CollectionUtil 提供了更加完善的功能,如果需要,也可以选用;
依赖:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>.7.22</version>
</dependency>
方法列表:
5赋初始值、尽量不要返回null对象
当定于局部变量,定义对象的属性时,能赋初始值的就尽量带上初始值;
Map map = new HashMap();
private integer age = 0;
当方法有返回值的时候,非必要的情况下,尽量不要返回null;
比如一个方法的执行最终返回的是List,当List没有值的时候,可以不返回null对象,而是可以返回一个空的List
public List select(){
// 这里处理其他逻辑
// 一旦返回的是null是,返回一个空List对象
return Collections.emptyList();
}
6Optional
Optional 是 Java 8 提供的一个对象容器,目的就是为了能有效的解决这个烦人的空指针异常,我们可以将 Optional 看成一个对象给包装类;
- 实例化 Optional
Object o = null;
Optional<Object> op = Optional.of(o1);
Optional<Object> op = Optional.ofNullable(o1);
Optional.of()
当对象为null时,创建过程就会抛出NPE异常
Optional.ofNullable()
当对象为null时,也能正常返回 Optional 对象
- 判空 isPresent()
Integer i = null;
Optional<Integer> op = Optional.of(i1);
System.out.println(op.isPresent()); // false
Integer i2 = 123;
Optional<Integer> op = Optional.ofNullable(i2);
System.out.println(op.isPresent()); // true
op.ifPresent(i->{
System.out.println(i);
});
isPresent() 当对象为null返回true,不为空时返回false
lambda表示式的链式处理:
op.ifPresent(obj->{
System.out.println(obj);
});
- 取值
// 取出原值,如果原对象为null会报NoSuchElementException异常
Integer integer = op.get();
// 取出原值,如果原值为空,则返回指点的默认值
Integer integer = op1.orElse(456);
// 取出原值,如果原值为空,返回默认值,不过在返回之前还需要做一些其他的事情
Integer integer = op2.orElseGet(() -> {
// 在这里做一些其他的操作
return;
});
// 取出原值,如果原值为空,就抛出指定的异常
op.orElseThrow(RuntimeException::new);
op.orElseThrow(() -> new RuntimeException("不好,我的值是空的!"));
- map() 和 flatMap()
- 编码过程中,经常会出现:a.xxx().yyy().zzz().mmm() 这样链式调用,这个过程,一旦中间有任意一环出现问题,就会NPE异常,因此,我们就可以借助map() 和 flatMap()来避免这个问题;
- 测试对象:
@Data
@NoArgs Constructor
@AllArgsConstructor
static class User {
private String name;
private Integer age;
private Optional<String> addr;
}
测试:
// 得到姓名的长度,如果没有姓名就返回
Integer nameLen = Optional.of(new User(null,, null))
.map(User::getName)
.map(String::length)
.orElse();
System.out.println(nameLen);
// 得到地址的长度,如果没有姓名就返回
Integer addr = Optional.of(new User(null,, Optional.of("北京")))
.flatMap(User::getAddr)
.map(String::length)
.orElse();
System.out.println(addr);
map会将返回的对象封装成Optional对象,如果返回的对象本身就是一个Optional对象了,那就使用flatMap()
7断言
Spring 中的 org.springframework.util.Assert 翻译为中文为” 断言 “,它用来断定某一个实际的运行值和预期项是否一致,不一致就抛出异常。借助这个类,同样也可以做判空检验;
Assert 类提供了以下的静态方法:
Integer i = null;
Assert.notNull(i,"i1 不为空");
Map map = null;
Assert.notEmpty(map,"map 不为空");
异常:
Exception in thread "main" java.lang.IllegalArgumentException: map 不为空
at org.springframework.util.Assert.notEmpty(Assert.java:)
at com.ehang.helloworld.controller.NullTest.t(NullTest.java:119)
at com.ehang.helloworld.controller.NullTest.main(NullTest.java:)
特别注意:
Assert 用来断定某一个实际的运行值和预期项是否一致,所以他和其他工具类的校验方式是反着在;比如 isNull 方法是期望对象为null,如果不为空的时候,就会报错; notNull 表示期望对象不为空,当对象为空时,就会报错;
8 局部变量 使用基本数据类型
在之前的文章《 阿里为何禁止在对象中使用基本数据类型 》中,从性能的角度,推荐局部变量的定义尽量使用基本数据类型,能不用包装类就不用;那么从今天文章的角度来说,使用基本数据类型也能有效的避免空指针异常;
如下实例:
int x;
Integer y;
System.out.println( x + ); // 编译失败
System.out.println( y + ); // 编译失败
int i =;
Integer j = null;
System.out.println( i + ); // 正常
System.out.println( j + ); // 空指针异常
int m = i; // 正常
int n = j; // 空指针异常
当变量x、y 只定义、不赋值的时候,x + 1 和 y + 1 是没办法通过编译的;而包装类 j 是可以指定 null 对象,当包装类参与运算的时候,首先会做拆箱操作,也就是调用 intValue() 方法,由于对象是空的,调用方法自然就会报空指针;同时,将一个包装类赋值给一个基本数据类型时,同样也会做拆箱操作,自然也就空指针异常了;
但是,基本数据类型就必须指定一个具体值,后续不管运算、还是赋值操作,都不会出现空指针异常;
9提前校验参数
后台数据,绝大部分都是通过终端请求传递上来的,所以需要在最接近用户的地方,把该校验的参数都校验了;比如StringBoot项目,就需要在Controller层将客户端请求的参数做校验,一旦必传的参数没有传值,就应该直接给客户端报错并提醒用户,而不是将这些不符合要求的null值传到Service甚至保存到数据库,尽早的校验并拦截,就能大大降低出问题的概率
之前介绍的 hibernate -validator 就能完美解决参数校验问题,详见: SpringBoot!你的请求、响应、异常规范了吗?
10 IDEA 提醒
IDEA 对空对象或者可能会出现null值的对象会有提醒,可以根据提醒来提前感知并预防
public static String t(int i){
String name = null;
String name = null;
if(i>){
name = "ehang";
}
t(name1);
t(name2);
return name;
}
相信通过这10招,既能轻松解决NPE问题,又不会因此而带来任何的编程负担;简直妙不可言!