大家好,我是山茶!
关于我:我是山茶,一个专注于技术的菜鸟。你懂的越多,就懂得不懂的越多。关注➕我,一起了解更多知识。
NPE异常对在座的每一个程序员来说,都是熟悉的不能再熟悉的一个问题。NPE是NullPointerException的缩写。是业务开发中常见的5种异常之一。
不论是新手小白、还是老司机,对NPE异常都是爱的深沉、恨的热烈。但是为了提高代码的质量,NPE异常是必须要消灭掉。
在增加逻辑消灭过程中的主要问题:
- 让代码冗长(此处可以举例,使用伪代码进行进行举例)
很多时候,核心的业务逻辑代码量是不大的,但一旦加上各种判断、校验,随之而来的就是代码的可读性、维护性变差;
举个栗子:判断根据接口是否变化,来处理业务 接口列表变化情况,原有接口和现有接口只需要判断接口路径变没变即可,但是加上了接口请求和接口响应,那整体的逻辑代码就会翻倍增加。
arduino
if(原有接口==现有接口){
接口变量未变
}else{
接口变量改变
}
arduino
if(原有接口==现有接口){
if(参数变化 )
{
if(请求参数变化,响应参数未变)
{
接口变量改变+请求参数变化;
}else if(请求参数未变,响应参数变化){
接口变量改变+响应参数变化;
}else{
接口变量未变+请求参数未变;
}
}
}else{
if(请求参数变化,响应参数未变)
{
接口变量改变+请求参数变化;
}else if(请求参数未变,响应参数变化){
接口变量改变+响应参数变化;
}else{
接口变量改变+请求参数未变;
}
}
- 纯纯的变成苦力活
ctrl+c、ctrl+v改一改变量名,接口名称或接口路径发生变化的只要拷贝一下请求和响应的判断即可,纯纯的变成了体力活,这中情况下长久以往,作为一个有理想的程序员就会失去对编程的乐趣和兴趣,丧失对编程的激情;
基于上面这些不太好的体验,让空指针和null消除的难度增加了不少。
这时会有小伙伴说,直接把请求和响应做成一个共有方法,直接调用不就好了。哎,这种方式是不错的哦,但是还有一个问题,在当期前业务中需要一个方法解决这个问题,但是其他的逻辑又要重复造更多的轮子呢?
从上面的问题中,在业务开发中,遇到的问题会有很多的重复性,但是如果我们一个个造轮子解决岂不是会造成代码冗余度达到一个不能描述的程度"屎山"。NPE问题的解决也是一样的。
所以为了既能解决NPE问题,又不影响我们的开发效率;JDK、三方框架为我们提供了很多优秀的工具类,大可不必自己耗时耗力去再造轮子了,在了解问题解决方案的时候首先要了解NullPointerException的生成原因和常见引发情况。
NPE常见场景及解决方案
常见场景
解决方案
1.字符串变量未初始化
通常情况下,我们定义字符串使用的时候,没有进行初始化操作,那么在下文中使用的使用,如果出现问题则大概率在做逻辑判断时出现空指针异常
rust
// 未初始化
String str;
// 初始化,可避免NPE
String str = "初始化";
2.接口类型的对象没有用具体的类初始化
javascript
接口类型的对象没有用具体的类初始化,比如:
// 很容易出现NPE异常
Map mapTest;
// 非人为赋值情况不会出现NPE
Map mapTest = new Map();
3.当一个对象的值为空时,你没有判断为空的情况。
rust
// 初始化定义为空
String str = "";
// 未增加判断
if(str.isEmpty()){
}
4.优先使用String.valueOf()方法代替toString()
当编写的时候需要对象的字符串表示形式时,尽量避免使用toString方法转换,尽可能的使用String.valueOf()。避免在对象引用为null时,调用toString() 会出现NullPointerException异常
5.class被声明了类型, 默认 class = null; 这样在调用class中方法的时候系统只能给你个空指针异常
csharp
// class被生命类型的时候,默认
class = null;
// 避免出现NullPointerException,使用时直接进行定义
class = new class();
6.参数值是 Integer 等包装类型,自动拆箱导致NPE
ini
boolean b = false;
Integer num = null;
Integer result = b ? 0 : num;
// num要拆箱,会报空指针异常
7.方法或远程服务返回的 List 不是空而是 null,没有进行判空就直接调用 List 的方法
scss
list = Method.test();
list.size();
// 此时没有进行list的判断是否为null,此时发生报错
// 需要进行判空值
if(list != null){
}
8.字符串与文字的比较,文字可以是一个字符串或Enum的元素,如下会出现异常
rust
String str = "null";
if(str.equals("Test")){
//这里的代码将不会被触发,因为会抛出java.lang.NullPointerException异常。
}
// 且通常情况下,根据阿里java代码优化白皮书,此处应该如此写
if("Test".equals(str)){
//这里的代码将不会被触发,因为会抛出java.lang.NullPointerException异常。
}
小结
在业务实现的过程中会出现各种各样的bug或者是异常,这个时候我们可以先去google一下,毕竟有很多的前辈同样都遇到过同样的问题,针对一个问题可能存在着不同的解决方法,在山茶的圈子里面也是一样。
避免NPE问题的小妙招
上面我们看到了很多存在或引起NPE问题的现象,那么我们想要在开发中避免出现NPE问题,还要具体看有哪些小妙招可以使用。
在这个思维导图中我们能看到几个常用的小妙招,但是具体的如何使用却是没有说明,接下来,我们详细的讲解下,该如何使用小妙招避免NPE异常
具体实现
Objects 工具类
常用方法
常用的方法主要有Objects.isNull、Objects.nonNull、Objects.requireNonNull这3个
- Objects.isNull判断对象是否为空,为null返回true,否则返回false
ini
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
ini
Object obj = null;
System.out.println(Objects.nonNull(obj)); // false
obj = new Object();
System.out.println(Objects.nonNull(obj)); // true
- Objects.requireNonNull校验非空,一旦对象为空,就会抛出空指针异(NullPointerException),改方法可以自定义异常描述,方便异常之后能快速定位问题。
ini
Object obj = null;
Objects.requireNonNull(obj);
// 自定义错误描述
Objects.requireNonNull(obj,"obj 定义出现报错");
StringUtil工具类
相比于Spring 框架带的工具类,要好用的多,并且他行改了String的所有封装方法
常用方法
判空校验的方法主要有4个StringUtils.isEmpty、StringUtils.isNotEmpty、StringUtils.isBlank、StringUtils.isNotBlank
xml
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
StringUtils.isEmpty和StringUtils.isNotEmpty
判断字符串对象是否为空,以及字符串长度是否为0;isEmpty 和 isNotEmpty 校验结果相反
ini
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
是在前面两个方法的基础之上,将字符床的开头结尾的空格去除后,判断数据的长度是否大于零
ini
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
Optional方式
Optional是Java8提供的一个对象容器,目的就是为了解决当前空指针的问题,我们实际上可以将它看做一个包装了解决方法的包装类
常用方法
Optional.of()、Optional.ofNullable()、Optional.isPresent()、map() 和 flatMap()
Optional.of():当对象为null时,创建过程就会抛出NPE异常
Optional.ofNullable():当对象为null时,也能正常返回 Optional 对象
判空 isPresent()
ini
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
ini
op2.ifPresent(obj->{
System.out.println(obj);
});
取值操作
ini
Integer integer = op2.get();
Integer integer1 = op1.orElse(456);
Integer integer2 = op2.orElseGet(() -> {
// 其他操作
return 456;
});
// 取出原值,如果原值为空,就抛出指定的异常
op2.orElseThrow(() -> new RuntimeException("error,my value is empty!"));
map() 和 flatMap()
a.xxx().yyy().zzz().mmm() 这样链式调用经常出现在我们的业务开发中,如果其中一环出现问题,则必然会出现报错,NPE异常也是极为容易出现的,因此我们需要借助map() 和 flatMap()来避免NPE问题
vbnet
map会将返回的对象封装成Optional对象,如果返回的对象本身就是一个Optional对象了,那就使用flatMap()
static class User {
private String name;
private Integer age;
private Optional<String> addr;
}
// 得到地址的长度,如果没有姓名就返回0
Integer addr = Optional.of(new User(null, 10, Optional.of("北京")))
.flatMap(User::getAddr)
.map(String::length)
.orElse(0);
System.out.println(addr);
集合类使用CollectionUtils判空
Map、List、Set
Map、List、Set 集合是常用到的数据结构,且他们都是包含有isEmpty()方法,能判断容器中是否包含了元素,但是存在一个问题,如果当具体IDE对象没有实例化的时,调用isEmpty()方法就会出现NPE异常;因此为了解决这个问题,Spring为我们提供了一个工具类org.springframework.util.CollectionUtils工具类,使用.CollectionUtils.isEmpty()方法优先判断是否为空,然后再用各自的isEmpty()方法判断,就会避免出现空指针异常的问题。
csharp
Map map = null;
System.out.println(map.isEmpty()); // 空指针异常
System.out.println(CollectionUtils.isEmpty(map)); // true
map = new HashMap();
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();
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();
set.add("1");
System.out.println(CollectionUtils.isEmpty(set)); // false
System.out.println(set.isEmpty()); // false
除了上面的Collection方法,还有更多的实用的方法,比如获取第一个元素:firstElement() 、最后一个元素:lastElement()等等,可以按需使用。
PS:单纯判空Spring的CollectionUtils已经足够,如果有更多复杂的场景功能,可以选用hutool的CollectionUtil
小结
在开发中会遇到各种各样的的原因导致出现NullPointerException空指针异常,但是究其根本原因,无外乎是在做判断的时候,没有对空返回处理完善,如果今后大家在开发中再遇到NPE问题,不妨使用上面的方法试一试。总有一种方式是适合你的