Java 编程中哪个异常是你印象最深刻的,那 NullPointerException
空指针可以说是臭名昭著的。不要说初级程序员会碰到,即使是中级,专家级程序员稍不留神,就会掉入这个坑里。
Java 中任何对象都有可能为空,当我们调用空对象的方法时就会抛出 NullPointerException
空指针异常,这是一种非常常见的错误类型。我们可以使用若干种方法来避免产生这类异常,使得我们的代码更为健壮。
空指针出现的几种情况
1、调用了空对象的实例方法
publicclassPerson{//属性privateStringname;privateintage;publicvoidprint(){System.out.println("ThisisPersonClass");}publicStringread(){System.out.println("Thisispersonread");returnnull;}publicstaticvoidmain(String[]args){Personperson=null;//调用了空对象的实例方法person.print();}}
2、访问了空对象的属性
Personperson=null;//调用了空对象的属性System.out.println(person.age);
3、当数组是一个空对象时,取它的长度
Personperson=null;//当数组是一个空对象时,取它的长度System.out.println(person.name.length());
4、方法的返回值是 null, 调用方直接去使用
Personperson=newPerson();//方法的返回值是null,调用方直接去使用System.out.println(person.read().contains("Java"));
如何从根源避免空指针
1、使用之前一定要初始化,或检查是否初始化;
2、尽量避免在函数中返回 NULL, 或给出详细的注释;
3、外部传值,除非有明细的说明,否则,一定要判断是否为 NULL。
equals和equalsIgnoreCase()方法
Object类中的equals 方法在非空对象引用上实现相等关系,具有对称性 x.equals(y) 和 y.equals(x) 结果是一样的,但当x == null时会抛出空指针异常。
例如:
Stringx=null;Stringy="world";if(x.equals(y)){//java.lang.NullPointerException}if(y.equals(x)){//即便x是null也能避免NullPointerException}
所以我们要把确定不为null的对象或值放在前面。
valueOf()和toString()
调用null对象的toString()
会抛出空指针异常,使用valueOf()
可以获得相同的值,传递一个 null 给 valueOf() 将会返回null。尤其是在那些包装类,像Integer、Float、Double和BigDecimal。
例如:
Integeri=null;System.out.println(i.toString());//抛出NullPointerException异常System.out.println(String.valueOf(i));//返回null不会出现异常
Optional 类型
Java 8 引入了 Optional
在业务系统中,对象中嵌套对象是经常发生的场景,如下示例代码:
//最外层对象classOuter{Nestednested;NestedgetNested(){returnnested;}}//第二层对象classNested{Innerinner;InnergetInner(){returninner;}}//最底层对象classInner{Stringfoo;StringgetFoo(){returnfoo;}}
业务中,假设我们需要获取 Outer
对象对底层的 Inner
中的 foo
属性,我们必须写一堆的非空校验,来防止发生 NullPointerException
:
//繁琐的代码Outerouter=newOuter();if(outer!=null&&outer.nested!=null&&outer.nested.inner!=null){System.out.println(outer.nested.inner.foo);}
在 Java8 中,我们有更优雅的解决方式,那就是使用 Optional
是说,我们可以在一行代码中,进行流水式的 map
操作。而 map 方法内部会自动进行空校验:
Optional.of(newOuter()).map(Outer::getNested).map(Nested::getInner).map(Inner::getFoo.ifPresent(System.out::println);//如果不为空,最终输出foo的值
suppiler 函数自定义增强 API
我们可以利用 suppiler
函数来出一个终极解决方案:
publicstatic<T>Optional<T>resolve(Supplier<T>resolver){try{Tresult=resolver.get();returnOptional.ofNullable(result);}catch(NullPointerExceptione){//可能会抛出空指针异常,直接返回一个空的Optional对象returnOptional.empty();}}
利用上面的 resolve
方法来重构上述的非空校验代码段:
Personperson=null;//调用了空对象的属性System.out.println(person.age);0
使用null安全的方法和库
有很多开源库已经为您做了繁重的空指针检查工作。其中最常用的一个的是Apache commons 中的StringUtils
。你可以使用StringUtils.isBlank()
,isNumeric()
,isWhiteSpace()
以及其他的工具方法而不用担心空指针异常。
Personperson=null;//调用了空对象的属性System.out.println(person.age);1
断言
断言是用来检查程序的安全性的,在使用之前进行检查条件,如果不符合条件就报异常,符合就继续。
Java 中自带的断言关键字:assert,如:
Personperson=null;//调用了空对象的属性System.out.println(person.age);2
输出:
Personperson=null;//调用了空对象的属性System.out.println(person.age);3
不过默认是不启动断言检查的,需要要带上 JVM 参数:-enableassertions 才能生效。
Java 中这个用的很少,建议使用 Spring 中的,更强大,更方便好用。
Spring中的用法:
Personperson=null;//调用了空对象的属性System.out.println(person.age);4
创建返回空集合而不是null的方法
一个非常好的技术是创建返回一个空集合的方法,而不是一个null值。你的应用程序的代码可以遍历空集合并使用它的方法和字段,而不会抛出一个NullPointerException。Collections类提供了方便的空 List,Set 和 Map: Collections.EMPTY_LIST
,Collections.EMPTY_SET
,Collections.EMPTY_MAP
。
例如:
Personperson=null;//调用了空对象的属性System.out.println(person.age);5
赋值时自动拆箱出现空指针
1、变量赋值自动拆箱出现的空指针
Personperson=null;//调用了空对象的属性System.out.println(person.age);6
Long 自动拆箱为 long 实质是调用了 longValue 方法,由于以上的 Long 类型变量值为 null,自动拆箱就会报 NPE。
2、方法传参时自动拆箱引起空指针异常
Personperson=null;//调用了空对象的属性System.out.println(person.age);7
以上自动拆箱报 NPE,原因在于装箱的值为 null,调用 null 的任何 方法都会报错。
规避指南:
基本数据类型优于包装器类型,优先考虑使用基本类型。
对于不确定的包装器类型,一定要校验是否为 NULL。
基本数据类型比包装器类型更加节省时间与空间。
结语
关于怎么有效避免空指针,其实还有更多,如何避免空指针,一是要注意代码编写规范,二是要提高代码素养。我们一起加油~