java中避免空指针的几个方法介绍

文章目录

    • 空指针出现的几种情况
    • 如何从根源避免空指针
    • equals和equalsIgnoreCase()方法
    • valueOf()和toString()
    • Optional 类型
    • suppiler 函数自定义增强 API
    • 使用null安全的方法和库
    • 断言
    • 创建返回空集合而不是null的方法
    • 赋值时自动拆箱出现空指针
    • 小结


Java 编程中哪个异常是你印象最深刻的,那 NullPointerException 空指针可以说是臭名昭著的。不要说初级程序员会碰到,即使是中级,专家级程序员稍不留神,就会掉入这个坑里。

Java 中任何对象都有可能为空,当我们调用空对象的方法时就会抛出 NullPointerException 空指针异常,这是一种非常常见的错误类型。我们可以使用若干种方法来避免产生这类异常,使得我们的代码更为健壮。

空指针出现的几种情况

1、调用了空对象的实例方法

public class Person {
    // 属性
    private String name;
    private int age;

    public void print(){
        System.out.println("This is Person Class");
    }
    
    public String read(){
        System.out.println("This is person read");
        return null;
    }

    public static void main(String[] args)
    {
        Person person = null;
        //调用了空对象的实例方法
        person.print();
    }
}

2、访问了空对象的属性

Person person = null;
//调用了空对象的属性
System.out.println(person.age);

3、当数组是一个空对象时,取它的长度

Person person = null;
//当数组是一个空对象时,取它的长度
System.out.println(person.name.length());

4、方法的返回值是 null, 调用方直接去使用

Person person = new Person();
//方法的返回值是 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时会抛出空指针异常。

例如:

String x = null;
String y = "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。

例如:

Integer i = null;
System.out.println(i.toString()); // 抛出NullPointerException异常
System.out.println(String.valueOf(i)); // 返回 null不会出现异常

Optional 类型

Java 8 引入了 Optional 类型,我们可以用它来对函数的返回值进行包装。这种方式的优点是可以明确定义该方法是有可能返回空值的,因此调用方必须做好相应处理,这样也就不会引发空指针异常。但是,也不可避免地需要编写更多代码,而且会产生很多垃圾对象,增加 GC 的压力,因此在使用时需要酌情考虑。

在业务系统中,对象中嵌套对象是经常发生的场景,如下示例代码:

// 最外层对象
class Outer {
    Nested nested;
    Nested getNested() {
        return nested;
    }
}
// 第二层对象
class Nested {
    Inner inner;
    Inner getInner() {
        return inner;
    }
}
// 最底层对象
class Inner {
    String foo;
    String getFoo() {
        return foo;
    }
}

业务中,假设我们需要获取 Outer 对象对底层的 Inner 中的 foo 属性,我们必须写一堆的非空校验,来防止发生 NullPointerException

// 繁琐的代码
Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}

在 Java8 中,我们有更优雅的解决方式,那就是使用 Optional是说,我们可以在一行代码中,进行流水式的 map 操作。而 map 方法内部会自动进行空校验

Optional.of(new Outer())
    .map(Outer::getNested)
    .map(Nested::getInner)
    .map(Inner::getFoo
    .ifPresent(System.out::println); // 如果不为空,最终输出 foo 的值

suppiler 函数自定义增强 API

我们可以利用 suppiler 函数来出一个终极解决方案:

public static <T> Optional<T> resolve(Supplier<T> resolver) {
    try {
        T result = resolver.get();
        return Optional.ofNullable(result);
    }
    catch (NullPointerException e) {
        // 可能会抛出空指针异常,直接返回一个空的 Optional 对象
        return Optional.empty();
    }
}

利用上面的 resolve 方法来重构上述的非空校验代码段:

Outer obj = new Outer();
// 直接调用 resolve 方法,内部做空指针的处理
resolve(() -> obj.getNested().getInner().getFoo());
    .ifPresent(System.out::println); // 如果不为空,最终输出 foo 的值

使用null安全的方法和库

有很多开源库已经为您做了繁重的空指针检查工作。其中最常用的一个的是Apache commons 中的StringUtils。你可以使用StringUtils.isBlank()isNumeric()isWhiteSpace()以及其他的工具方法而不用担心空指针异常。

//StringUtils方法是空指针安全的,他们不会抛出空指针异常
System.out.println(StringUtils.isEmpty(null));
System.out.println(StringUtils.isBlank(null));
System.out.println(StringUtils.isNumeric(null));
System.out.println(StringUtils.isAllUpperCase(null));

输出:
true
true
false
false

断言

断言是用来检查程序的安全性的,在使用之前进行检查条件,如果不符合条件就报异常,符合就继续。

Java 中自带的断言关键字:assert,如:

assert name == null : "名称不能为空";

输出:

Exception in thread "main" java.lang.AssertionError: 名称不正确

不过默认是不启动断言检查的,需要要带上 JVM 参数:-enableassertions 才能生效。

Java 中这个用的很少,建议使用 Spring 中的,更强大,更方便好用。

Spring中的用法:

Assert.notNull(name,"名称不能为空");

创建返回空集合而不是null的方法

一个非常好的技术是创建返回一个空集合的方法,而不是一个null值。你的应用程序的代码可以遍历空集合并使用它的方法和字段,而不会抛出一个NullPointerException。Collections类提供了方便的空 List,Set 和 Map: Collections.EMPTY_LISTCollections.EMPTY_SETCollections.EMPTY_MAP

例如:

public class Example {
   private static List<Integer> numbers = null;

   public static List<Integer> getList() {
     if (numbers == null){
         return Collections.EMPTY_LIST;
     } else {
         return numbers;
     }
   }
}

赋值时自动拆箱出现空指针

1、变量赋值自动拆箱出现的空指针

Long i = null;
//变量赋值自动拆箱出现的空指针
long j = i;

Long 自动拆箱为 long 实质是调用了 longValue 方法,由于以上的 Long 类型变量值为 null,自动拆箱就会报 NPE。

2、方法传参时自动拆箱引起空指针异常

public static int add(int x, int y)
{
    return x + y;
}


public static void main(String[] args)
{
    Integer left = null;
    Integer right = null;
    //方法传参时自动拆箱引起空指针异常
    System.out.println(add(left, right));
}

以上自动拆箱报 NPE,原因在于装箱的值为 null,调用 null 的任何 方法都会报错。

规避指南:

  • 基本数据类型优于包装器类型,优先考虑使用基本类型。
  • 对于不确定的包装器类型,一定要校验是否为 NULL。

基本数据类型比包装器类型更加节省时间与空间。

小结

关于怎么有效避免空指针,其实还有更多,如何避免空指针,一是要注意代码编写规范,二是要提高代码素养。

你可能感兴趣的:(java基础,java,后端)