什么是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
可以看到比之前的提示友好了很多,可以直接定位问题所在。





