10招让你彻底告别空指针异常!

NPE异常相信 Java 程序员都很熟悉,是 NullPointerException 的缩写;最近业务需求开发的有点着急,测试环境就时不时的来个NPE异常,特别的头疼;作为出镜率最高的异常之一,一旦入行 Java 开发,可以说它将伴随着你整个职业生涯;不管是新手小白、还是老司机,对NPE异常那是又“爱”又狠,爱的主要原因是处理起来简单,恨当然是一个不小心就会踩坑;为了提高代码的质量,NPE异常是必须要消灭掉的;

那既然处理起来简单,有什么好纠结的呢?老老实实校验不就完了,但整个处理的过程中对程序员来说体验是非常糟糕的;

让代码冗长

很多时候,核心的业务逻辑代码量是不大的,但是一旦加上各种判断、校验,就会让代码变的冗长,可读性、维护性随之下降;

纯苦力活

像这种机械式的判空、校验本质上就是一些体力活,没有任何编码乐趣可言,长时间编写这种代码,会丧失对编程的激情;

易背锅

很多业务需要多人合作,有时候可能会出现侥幸心里,都认为其他人在用的时候会处理;无形中挖了些坑,一不小心就锅从天降;

基于上面这些不太好的体验,让消除的难度增加了不少;

有时候当需求很着急的时候,程序员大部分都会选择以功能为主,一些不太重要的东西总是想着晚点再来补充,先跳过写重要的内容,结果是一跳过就没有然后了;

为了既能解决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:228)

at com.ehang.helloworld.controller.NullTest.t5(NullTest.java:97)

at com.ehang.helloworld.controller.NullTest.main(NullTest.java:23)

2字符串判空

字符串是开发过程中使用最多一种数据类型,因此对字符串的判断、校验也就必不可少了,原生的方式都是通过空对象,长度进行判断:

String str = "穿越世界尽头"

if ( null != str && s1.length() > 0 ){

// 对str字符串进行使用

}

但是,对字符串的校验,除了判空之外,还有很多其他的场景,比如判断是不是空串(String str = ""),是不是只有空格(String str = " ")等等,那这些校验,就会麻烦一些了;不过木有关系,现成的工具类已经足够满足了;

Spring StringUtil工具类

org.springframework.util.StringUtils 是String 框架自带的字符串工具类,功能比较单一,在教新的版本中,这个工具类的字符串判空方法已经被弃用了,所以不太建议使用了;

StringUtils.isEmpty

空对象以及空串的校验;

String s1 = null;

String s2 = "";

String s3 = " ";

System.out.println(StringUtils.isEmpty(s1)); // true

System.out.println(StringUtils.isEmpty(s2)); // true

System.out.println(StringUtils.isEmpty(s3)); // false

apache lang3 StringUtil工具类

apache lang3 StringUtil 工具类(org.apache.commons.lang3.StringUtils) 相比于Spring 框架带的工具类,要强大太对了,涵盖了对String 操作的所有封装;

判空校验的话主要有4个StringUtils.isEmpty、StringUtils.isNotEmpty、StringUtils.isBlank、StringUtils.isNotBlank

依赖

org.apache.commons

commons-lang3

StringUtils.isEmpty和StringUtils.isNotEmpty

判断字符串对象是否为空,以及字符串长度是否为0;isEmpty 和 isNotEmpty 校验结果相反;

String s1 = null;

String s2 = "";

String s3 = " ";

System.out.println(StringUtils.isEmpty(s1)); // true

System.out.println(StringUtils.isEmpty(s2)); // true

System.out.println(StringUtils.isEmpty(s3)); // false

System.out.println();

System.out.println(StringUtils.isNotEmpty(s1)); // false

System.out.println(StringUtils.isNotEmpty(s2)); // false

System.out.println(StringUtils.isNotEmpty(s3)); // true

StringUtils.isBlank、StringUtils.isNotBlank

在 StringUtils.isEmpty和StringUtils.isNotEmpty 判断的基础上,还会将字符串开头,结尾的空格去掉之后,判断长度是否大于0

String s1 = null;

String s2 = "";

String s3 = " ";

String s4 = " 1 2 ";

System.out.println(StringUtils.isBlank(s1)); // true 空对象

System.out.println(StringUtils.isBlank(s2)); // true 长度等于0

System.out.println(StringUtils.isBlank(s3)); // true 去掉前后空格之后,长度也等于0

System.out.println(StringUtils.isBlank(s4)); // false 去掉前后空格(1 2),长度大于0

System.out.println();

System.out.println(StringUtils.isNotBlank(s1)); // false

System.out.println(StringUtils.isNotBlank(s2)); // false

System.out.println(StringUtils.isNotBlank(s3)); // false

System.out.println(StringUtils.isNotBlank(s4)); // true

其他功能

本文主要是探讨判空校验,lang3 的 StringUtil 工具类几乎涵盖了所有关于String操作的封装,大大降低了我们处理 String 的复杂度,更多功能可参考官方文档

https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html

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("1", "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("1");

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("1");

System.out.println(CollectionUtils.isEmpty(set)); // false

System.out.println(set.isEmpty()); // false

除了判空之外,该工具类还包含了很多很实用的方法,比如获取第一个元素:firstElement() 、最后一个元素:lastElement()、是否包含某个元素:contains() 等等

hutool的CollectionUtil

单纯判空,前面Spring的CollectionUtils已经足够,其他的功能也够满足绝大部分的使用场景;hutool的CollectionUtil提供了更加完善的功能,如果需要,也可以选用;

依赖:

cn.hutool

hutool-all

5.7.22

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 o1 = null;

Optional op1 = Optional.of(o1);

Optional op2 = Optional.ofNullable(o1);

Optional.of()

当对象为null时,创建过程就会抛出NPE异常

Optional.ofNullable()

当对象为null时,也能正常返回 Optional 对象

判空 isPresent()

Integer i1 = null;

Optional op1 = Optional.of(i1);

System.out.println(op1.isPresent()); // false

Integer i2 = 123;

Optional op2 = Optional.ofNullable(i2);

System.out.println(op2.isPresent()); // true

op2.ifPresent(i->{

System.out.println(i);

});

isPresent() 当对象为null返回true,不为空时返回false

lambda表示式的链式处理:

op2.ifPresent(obj->{

System.out.println(obj);

});

取值

// 取出原值,如果原对象为null会报NoSuchElementException异常

Integer integer = op2.get();

// 取出原值,如果原值为空,则返回指点的默认值

Integer integer1 = op1.orElse(456);

// 取出原值,如果原值为空,返回默认值,不过在返回之前还需要做一些其他的事情

Integer integer2 = op2.orElseGet(() -> {

// 在这里做一些其他的操作

return 456;

});

// 取出原值,如果原值为空,就抛出指定的异常

op2.orElseThrow(RuntimeException::new);

op2.orElseThrow(() -> new RuntimeException("不好,我的值是空的!"));

map() 和 flatMap()

编码过程中,经常会出现:a.xxx().yyy().zzz().mmm() 这样链式调用,这个过程,一旦中间有任意一环出现问题,就会NPE异常,因此,我们就可以借助map() 和 flatMap()来避免这个问题;

测试对象:

@Data

@NoArgsConstructor

@AllArgsConstructor

static class User {

private String name;

private Integer age;

private Optional addr;

}

测试:

// 得到姓名的长度,如果没有姓名就返回0

Integer nameLen = Optional.of(new User(null, 10, null))

.map(User::getName)

.map(String::length)

.orElse(0);

System.out.println(nameLen);

// 得到地址的长度,如果没有姓名就返回0

Integer addr = Optional.of(new User(null, 10, Optional.of("北京")))

.flatMap(User::getAddr)

.map(String::length)

.orElse(0);

System.out.println(addr);

map会将返回的对象封装成Optional对象,如果返回的对象本身就是一个Optional对象了,那就使用flatMap()

7断言

Spring 中的 org.springframework.util.Assert 翻译为中文为"断言",它用来断定某一个实际的运行值和预期项是否一致,不一致就抛出异常。借助这个类,同样也可以做判空检验;

Assert 类提供了以下的静态方法:

Integer i1 = null;

Assert.notNull(i1,"i1 不为空");

Map map = null;

Assert.notEmpty(map,"map 不为空");

异常:

Exception in thread "main" java.lang.IllegalArgumentException: map 不为空

at org.springframework.util.Assert.notEmpty(Assert.java:555)

at com.ehang.helloworld.controller.NullTest.t6(NullTest.java:119)

at com.ehang.helloworld.controller.NullTest.main(NullTest.java:23)

特别注意:

Assert 用来断定某一个实际的运行值和预期项是否一致,所以他和其他工具类的校验方式是反着在;比如isNull方法是期望对象为null,如果不为空的时候,就会报错;notNull表示期望对象不为空,当对象为空时,就会报错;

8局部变量使用基本数据类型

从今天文章的角度来说,使用基本数据类型也能有效的避免空指针异常;

如下实例:

int x;

Integer y;

System.out.println( x + 1 ); // 编译失败

System.out.println( y + 1 ); // 编译失败

int i = 1;

Integer j = null;

System.out.println( i + 1 ); // 正常

System.out.println( j + 1 ); // 空指针异常

int m = i; // 正常

int n = j; // 空指针异常

当变量x、y 只定义、不赋值的时候,x + 1 和 y + 1 是没办法通过编译的;而包装类 j 是可以指定null对象,当包装类参与运算的时候,首先会做拆箱操作,也就是调用 intValue() 方法,由于对象是空的,调用方法自然就会报空指针;同时,将一个包装类赋值给一个基本数据类型时,同样也会做拆箱操作,自然也就空指针异常了;

但是,基本数据类型就必须指定一个具体值,后续不管运算、还是赋值操作,都不会出现空指针异常;

9提前校验参数

后台数据,绝大部分都是通过终端请求传递上来的,所以需要在最接近用户的地方,把该校验的参数都校验了;比如StringBoot项目,就需要在Controller层将客户端请求的参数做校验,一旦必传的参数没有传值,就应该直接给客户端报错并提醒用户,而不是将这些不符合要求的null值传到Service甚至保存到数据库,尽早的校验并拦截,就能大大降低出问题的概率

10IDEA提醒

IDEA 对空对象或者可能会出现null值的对象会有提醒,可以根据提醒来提前感知并预防

public static String t1(int i){

String name1 = null;

String name2 = null;

if(i>0){

name2 = "ehang";

}

t2(name1);

t2(name2);

return name2;

}

相信通过这10招,既能轻松解决NPE问题,又不会因此而带来任何的编程负担;简直妙不可言!