前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >还只会 null != obj 判空,10招让你彻底告别空指针异常!

还只会 null != obj 判空,10招让你彻底告别空指针异常!

作者头像
一行Java
发布2023-09-04 14:03:38
7880
发布2023-09-04 14:03:38
举报
文章被收录于专栏:用户9257747的专栏

大家好,我是一航!

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

代码语言:javascript
复制
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

代码语言:javascript
复制
Object obj = null;
System.out.println(Objects.nonNull(obj)); // false

obj = new Object();
System.out.println(Objects.nonNull(obj)); // true

Objects.requireNonNull

校验非空,一旦对象为空,就会抛出空指针异常(NullPointerException),改方法可以自定义异常描述,方便异常之后能快速定位问题所在:

代码语言:javascript
复制
Object obj = null;
Objects.requireNonNull(obj);
// 自定义错误描述
Objects.requireNonNull(obj,"obj 对象为空");

执行输出:

代码语言:javascript
复制
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字符串判空

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

代码语言:javascript
复制
String str = "一行Java"
if ( null != str && s1.length() > 0 ){
 // 对str字符串进行使用
}

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

Spring StringUtil工具类

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

StringUtils.isEmpty

空对象以及空串的校验;

代码语言:javascript
复制
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.isEmptyStringUtils.isNotEmptyStringUtils.isBlankStringUtils.isNotBlank

依赖

代码语言:javascript
复制
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

StringUtils.isEmptyStringUtils.isNotEmpty

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

代码语言:javascript
复制
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.isBlankStringUtils.isNotBlank

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

代码语言:javascript
复制
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异常;

如下示例:

代码语言:javascript
复制
public Boolean isEhang(String name) {
    if (name.equals("ehang")) {
        return true;
    }
    return false;
}

当如果name为null的时候,就会出现NPE异常;

可以做如下调整:

代码语言:javascript
复制
if ("ehang".equals(name))
...

这样就算name为null,即不会出现NPE异常,也能正常的判断;

4Map、List、Set 判空

Map、List、Set 是经常会用到的数据结构,虽然他们都包含有isEmpty()方法,能判断容器中是否包含了元素,但是无法判断自生对象是否为空,一旦对象没有实例化时,调用isEmpty()就会报空指针异常;Spring 为我们提供了一个org.springframework.util.CollectionUtils工具类,其中的isEmpty就会优先判断对象是否为空,然后再通过isEmpty()判断是否存在元素,能大大减少因为对象为空带来的空指针异常;

代码语言:javascript
复制
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提供了更加完善的功能,如果需要,也可以选用;

依赖:

代码语言:javascript
复制
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.22</version>
</dependency>

方法列表:

5赋初始值、尽量不要返回null对象

当定于局部变量,定义对象的属性时,能赋初始值的就尽量带上初始值;

代码语言:javascript
复制
Map map = new HashMap();

private Integer age = 0;

当方法有返回值的时候,非必要的情况下,尽量不要返回null;

比如一个方法的执行最终返回的是List,当List没有值的时候,可以不返回null对象,而是可以返回一个空的List

代码语言:javascript
复制
public List select(){
    // 这里处理其他逻辑
    // 一旦返回的是null是,返回一个空List对象
    return Collections.emptyList();
}

6Optional

Optional 是 Java 8 提供的一个对象容器,目的就是为了能有效的解决这个烦人的空指针异常,我们可以将 Optional 看成一个对象给包装类;

实例化 Optional 对象

代码语言:javascript
复制
Object o1 = null;
Optional<Object> op1 = Optional.of(o1);
Optional<Object> op2 = Optional.ofNullable(o1);

Optional.of()

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

Optional.ofNullable()

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

判空 isPresent()

代码语言:javascript
复制
Integer i1 = null;
Optional<Integer> op1 = Optional.of(i1);
System.out.println(op1.isPresent()); // false

Integer i2 = 123;
Optional<Integer> op2 = Optional.ofNullable(i2);
System.out.println(op2.isPresent()); // true
op2.ifPresent(i->{
    System.out.println(i);
});

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

lambda表示式的链式处理:

代码语言:javascript
复制
op2.ifPresent(obj->{
    System.out.println(obj);
});

取值

代码语言:javascript
复制
// 取出原值,如果原对象为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()来避免这个问题;

测试对象:

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
static class User {
    private String name;

    private Integer age;

    private Optional<String> addr;
}

测试:

代码语言:javascript
复制
// 得到姓名的长度,如果没有姓名就返回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 类提供了以下的静态方法:

方法名

描述

失败时抛出异常

isNull(Object object, String message)

object 不为空,抛出异常

IllegalArgumentException

notNull(Object object, String message)

object 为空,抛出异常

IllegalArgumentException

hasLength(String text, String message)

text 是空字符串,抛出异常

IllegalArgumentException

hasText(String text, String message)

不包含空白字符串,抛出异常

IllegalArgumentException

doesNotContain(String textToSearch, String substring, String message)

textToSearch 中包含 substring,抛出异常

IllegalArgumentException

notEmpty(Object[] array, String message)

array 为空或长度为 1,抛出异常

IllegalArgumentException

noNullElements(Object[] array, String message)

array 中包含 null 元系,抛异常

IllegalArgumentException

notEmpty(Collection collection, String message)

collection 不包含元素,抛出异常

IllegalArgumentException

notEmpty(Map map, String message)

map 中包含 null,抛出异常

IllegalArgumentException

isInstanceOf(Class type, Object obj, String message)

如果 obj 不是 type 类型,抛出异常

IllegalArgumentException

isAssignable(Class superType, Class subType, String message)

subType 不是 superType 子类,抛出异常

IllegalArgumentException

state(boolean expression, String message)

expression 不为 true 抛出异常

IllegalStateException

isTrue(boolean expression, String message)

expression 不为 true 抛出异常

IllegalArgumentException

代码语言:javascript
复制
Integer i1 = null;
Assert.notNull(i1,"i1 不为空");

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

异常:

代码语言:javascript
复制
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局部变量使用基本数据类型

在之前的文章《阿里为何禁止在对象中使用基本数据类型》中,从性能的角度,推荐局部变量的定义尽量使用基本数据类型,能不用包装类就不用;那么从今天文章的角度来说,使用基本数据类型也能有效的避免空指针异常;

如下实例:

代码语言:javascript
复制
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甚至保存到数据库,尽早的校验并拦截,就能大大降低出问题的概率

之前介绍的hibernate-validator就能完美解决参数校验问题,详见:SpringBoot!你的请求、响应、异常规范了吗?

10IDEA提醒

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

代码语言:javascript
复制
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问题,又不会因此而带来任何的编程负担;简直妙不可言!

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

本文分享自 一行Java 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1Objects 工具类
    • 特点
      • 示例
      • 2字符串判空
        • Spring StringUtil工具类
          • apache lang3 StringUtil工具类
          • 3字符串比较
          • 4Map、List、Set 判空
            • hutool的CollectionUtil
            • 5赋初始值、尽量不要返回null对象
            • 6Optional
            • 7断言
            • 8局部变量使用基本数据类型
            • 9提前校验参数
            • 10IDEA提醒
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档