暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

什么是JAVA中的空指针(NullPointerExceptions),如何避免程序中的空指针?

小朝的编码生涯 2022-11-13
581

什么是JAVA中的空指针(NullPointerExceptions),如何避免程序中的空指针?

空指针异常可以说是java中最常见的异常之一了,你对它又了解多少呢?


什么是空指针

众所周知,JAVA中有两大类类型:基本类型和引用类型。基本类型可以由编译器保证使用前必须初始化为一个有效值(但是不一定是业务的有效值),使用它不会导致空指针异常;而引用类型可以被初始化为一个特殊的值null
,说明该引用未引用任何值
当使用一个null
对象的属性,或调用其方法时,会触发空指针异常(NullPointerExceptions
)。下面是官方文档给出的抛出异常的情况:

  • Calling the instance method of a null object.(调用null
    的实例方法)

  • Accessing or modifying the field of a null object.(调用或者修改null
    的属性)

  • Taking the length of null as if it were an array.(获取null
    数组的长度)

  • Accessing or modifying the slots of null as if it were an array.(访问或修改null
    数组中的元素)

  • Throwing null as if it were a Throwable value.(常规抛出null异常)


调用null
的实例方法 调用或者修改null
的属性

可以看下面的代码示例:


// 类型定义
class SomeClass{
// 仅作演示,尽量不要定义public的属性。
public int someField;
public void someMethod(){
}
}

SomeClass someClass = null;
someClass.someMethod();
someClass.someField = 10;

输出:


Exception in thread "main" java.lang.NullPointerException
...

SomeClass someClass = null;
声明了一个引用someClass
并赋值为null
someClass.someMethod();
尝试调用null
的实例方法(java8+也可能通过 someclass::somemethod
 语法来调用),会抛出空指针异常;someClass.someField = 10;
修改null
的属性,也会抛出空指针异常。


获取null
数组的长度 访问或修改null
数组中的元素

可以看下面的代码示例:


int[] nullArray = null;
int length = nullArray.length;
nullArray[0] = 10;

输出:


// 获取长度空指针
Exception in thread "main" java.lang.NullPointerException
at ...

int[] nullArray = null;
声明了一个引用nullArray
并赋值为null
int length = nullArray.length;
获取null
数组的长度,会抛出空指针异常;nullArray[0] = 10;
修改null
数组中的元素,也会抛出空指针异常。


常规抛出null异常

出了"被动"触发空指针异常外,我们还可以手动抛出空指针异常,比如这样:throw new NullPointerException("null!");


编程中一些空指针异常触发场景以及规避方法


方法参数中的空指针


方法参数中的空指针触发场景

当我们对外开放一个方法时,方法的入参就有可能会被调用方传入null,比如下列方法:


int someMethod(Object someObj){
someObj.xxx();
return 0;
}

如果调用方传入null:int res = someMethod(null);
,那么在下面就会导致空指针异常了。


方法参数中的空指针防范方法

对于这类对外开放的(public/protected)方法来说,保护自己就很重要,防范方法一般是提前检查入参是否满足要求,如果不满足要求则抛出异常,可以通过类库中的Objects.requireNonNull()
来完成,比如:


int someMethod(Object someObj){
Objects.requireNonNull(someObj, "someObj must not be null");
someObj.xxx();
}

这一条当然不只适用于我们自己编写程序的时候,在使用第三方的类库时,也要注意使用的类库的方法参数是否支持null
,对方也许粗心大意未处理这类异常,自己要多加小心。


方法返回值中的空指针异常


方法返回值空指针的触发场景

使用别人的程序时,除了要注意入参以外,返回值也要额外留意,确认该方法的返回值是否会返回null,防止造成不必要的麻烦。比如:


Integer getArrayLength(int[] num){
if (num == null){
return null;
} else{
return num.length;
}
}

上述方法会返回传入的数组长度,当传入数组为空时,会返回null。如果调用时,传入了一个null
数组,并且尝试用它返回的值进行加减,那么就会导致空指针异常了,如下所示:


int[] array = null;
......很长的代码之后
Integer length = getArrayLength(array);
// 空指针!
int res = length + 10;
.....

这里的空指针看起来没有那么明显,实际上,在getArrayLength
返回一个装箱类型Integer
的时候,这个异常就埋下了伏笔,在int res = length + 10;
时,返回的length
被自动拆箱,导致了空指针异常。
这也是一种比较罕见的情况,但是出现这类问题可能会有漫长的debug等着我们了。
当返回值是装箱类型的时候,务必要特别留意。

除了上面这种以外,还有一类obj.getXX().getXXX()..
的调用方法需要额外小心,一旦出现问题很难定位,最好别用。


方法返回值空指针的防范方法

对于方法的设计者来说,尽量不要返回null
,返回数组和集合时,尽量返回一个空数组new SomeObj[0]
或者是空集合Collections.emptyXXX()
,如果是因为入参存在问题无法正常返回,及时抛出参数异常,不要默默返回null
;遇到必须返回null
的时候,可以返回Optional<T>
。 尽量不要返回装箱类型,除非迫不得已。

对于方法的使用方来说,最好确定方法会不会返回null
并做对应处理。实在无法确定的,最好自己判断一下空指针。遇到装箱返回值的,要警惕自动拆箱。


语句块中的空指针

这类空指针比较少见,语句块可以是for/switch/synchronized等。下面依次举例:


// for语句块中,iterable为null会导致空指针
for (element : iterable)
// switch语句块中,表达式xxx结果为空会导致空指针
switch (xxx) { ... }
// synchronized语句块中,传入null会导致空指针异常
synchronized (someNullReference) { ... }

防范方法主要是提前检查。


一些其他的防范方法


string比较时,常量在前面

见代码:


if ("some string".equals(xxx))


使用SonarLint插件(idea)

SonarLint插件不止可以检查空指针,还可以检查很多的常见编程问题。idea中可以直接在插件市场搜索安装。
提示: idea中代码被标注成背景是黄色时,通常说明可能存在问题,可以把鼠标挪上去查看详情。


jdk14中的空指针异常增强

jdk14中添加了对于空指针异常友好的提示,便于开发者快速定位空指针的对象。示例代码:


int[] nullArray = null;
nullArray[0] = 10;

输出如下:


Exception in thread "main" java.lang.NullPointerException: Cannot store to int array because "nullArray" is null

可以看到比之前的提示友好了很多,可以直接定位问题所在。



文章转载自小朝的编码生涯,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论