Java 使用 Optional 取代 Null 的实践

目录

1、不使用Optional时的NullPointerException防御

2、使用Optional重新定义数据模型

3、Optional 类的 API 使用概述


        厌烦的 NullPointerException 异常:在编程过程中,我们往往需要对对象的字段进行检查,判断它的值是否为期望的格式,如果漏过检查步骤,很有可能在程序的运行过程中抛出一个 NullPointerException 异常。

        那么在 Java8 之前,我们来看看代码是如何避免这个 NullPointerException 异常。 

1、不使用Optional时的NullPointerException防御

        本次示例中会用到以下 Person/Car/Insurance 的数据模型:

public class Person {
    private Car car;
}

public class Car {
    private Insurance insurance;
}

public class Insurance {
    private String name;
}
//以上数据模型都省略了get/set方法

        那么,下面这段代码会存在怎样的问题呢?

    private String getInsuranceName(Person person) {
        return person.getCar().getInsurance().getName();
    }

        这段代码看起来相当正常,但是 person 可能为 null,调用 getCar 和 getInsurance 方法,任何一个步骤都可能会返回 null,这会导致运行时出现 NulPointerException,从而终止程序的运行。那么,怎样做才能避免这种不期而至的 NullPointerException 呢? 

        我们最常用的方式,是在不确定一个变量是否为 null 时,都添加一个 if 判断,像是下边这样:

    private String getInsuranceName(Person person) {
        if (person != null) {
            Car car = person.getCar();
            if (car != null) {
                Insurance insurance = car.getInsurance();
                if (insurance != null) {
                    return insurance.getName();
                }
            }
        }
        return "unknown";
    }

        很明显,这种方式不具备扩展性,同时还牺牲了代码的可读性。为了避免深层递归的 if 语句块,我们还可能这样写:

    private String getInsuranceName(Person person) {
        if (person != null) {
            return "unknown";
        }
        Car car = person.getCar();
        if (car != null) {
            return "unknown";
        }
        Insurance insurance = car.getInsurance();
        if (insurance != null) {
            return insurance.getName();
        }
        return "unknown";
    }

        但是这种方案也不理想,它有四个截然不同的退出点,使得代码的维护异常艰难。所以,使用 null 来表示变量值是有问题的,Java8 后我们有更优雅的方式来对缺失的变量值建模。

2、使用Optional重新定义数据模型

        Java 8中引人了一个新的类 java.util.Optional<T> ,用于解决在编程中可能出现的空指针异常问题。它的主要目的是表示一个值可能存在,也可能不存在的情况,并提供一些方法来安全地处理这些可能为 null 的值。

        那么使用 Optional 类后重写 getInsuranceName 方法,代码如下所示:

    private static String getInsuranceName(Person person) {
        return Optional.ofNullable(person)
                .flatMap(r -> Optional.ofNullable(r.getCar()))
                .flatMap(r -> Optional.ofNullable(r.getInsurance()))
                .map(Insurance::getName)
                .orElse("nuKnown");
    }

        比较之前的两处代码,我们可以看到,在处理空指针时,使用 Optional 具有明显的优势。它不再需要使用那么多的条件分支,也不会增加代码的复杂性。

        需要注意的是,由于 Optional 类设计时就没特别考虑将其作为类的字段使用,所以它并未实现 Serializable 接口。由于这个原因,如果程序中使用了某些要求序列化的库或者框架,在你的数据模型中仍然使用了 Optional,就有可能引发应用程序故障,比如下边代码:

public class Person {
    private Optional<Car> car; //不能被序列化
}

        因此,如果要实现支持序列化的数据模型,我们可以为字段提供一个额外获取 Optional<T> 的方法,示例代码如下:

public class Person {
    private Car car;

    public Optional<Car> getOptionalCar() { //Optional方法
        return Optional.ofNullable(car);
    }
}
//以上数据模型省略了get/set方法

        接下来,我们尝试稍微改造一下 Person/Car/Insurance 的数据模型,在 Person/Car 的数据模型中分别添加一个获取字段的 Optional<T> 的方法:

public class Person {
    private Car car;

    public Optional<Car> getOptionalCar() { //Optional方法
        return Optional.ofNullable(car);
    }
}

public class Car {
    private Insurance insurance;

    public Optional<Insurance> getOptionalInsurance(){ //Optional方法
        return Optional.ofNullable(insurance);
    }
}

public class Insurance {
    private String name; //此模型默认Insurance的name不能为null
}
//以上数据模型都省略了get/set方法

        那么根据改造后的 Person/Car/Insurance 的数据模型重写 getInsuranceName 方法如下所示:

    private String getInsuranceName(Person person) {
        return Optional.ofNullable(person)
                .flatMap(Person::getOptionalCar)
                .flatMap(Car::getOptionalInsurance)
                .map(Insurance::getName)
                .orElse("unknown");
    }

        总结:使用 Optional 确实可以让代码变的精简,尤其是在一个对象包含很多个层级的嵌套对象时,使用 Optional 来减少 null 的判断逻辑还是非常值得去尝试的。

3、Optional 类的 API 使用概述

        (1)创建 Optional 对象

        创建 Optional 对象有多种方式:

    //1、创建空的Optional对象
    Optional<Car> empty = Optional.empty();

    //2、创建非空的Optional对象,如果car==null,会抛出NullPointerException
    Optional<Car> notNullOptional = Optional.of(car);

    //3、创建可以为空的Optional对象,如果car==null,返回Optional.empty()
    Optional<Car> optional = Optional.ofNullable(car);

        (2)Optional 操作:从Optional 对象中提取和转换值

        可以使用 map/flatMap 从 Optional 对象中提取和转换值:

    Optional<String> s = Optional.ofNullable(person)
          .flatMap(Person::getOptionalCar)
          .flatMap(Car::getOptionalInsurance)
          .map(Insurance::getName);

        除了 map 和 flatMap 以外,Optional 类还有第三个方法 filter,该方法用来对数据进行过滤,它的使用与 Stream 接口中的  filter 相似,在此不再过多介绍。

        (3)从 Optional 提取值的默认行为API

        比如下边这个代码,我们使用了 orElse("unknown") 来获取 map 映射的结果:

    private String getInsuranceName(Person person) {
        return Optional.ofNullable(person)
                .flatMap(Person::getOptionalCar)
                .flatMap(Car::getOptionalInsurance)
                .map(Insurance::getName)
                .orElse("unknown");
    }

        采用 orElse 方法读取变量的值,可以设置一个默认值(比如,"unknown"),在遭遇空的Optional 变量时,默认值会作为该方法的调用返回值。

        Optional 类提供了多种方法读取 Optional 实例中的变量值:

        get() 是最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个 NoSuchElementException 异常。所以,除非非常确定 Optional 变量一定包含值,否则不要使用这个方法。

        orElse(T other) 允许在 Optional 对象不包含值时提供一个默认值。

        orElseGet(Supplier<? extends T> other) orElse 方法的延迟调用版,Supplier 方法只有在 Optional 对象不含值时才执行调用。如果创建默认值非常耗时,应该考虑采用这种方式(借此提升程序的性能)

        orElseThrow(Supplier<? extends X> exceptionSupplier) get 方法非常类似,当Optional 对象为空时都会抛出一个异常,但是使用 orElseThrow 可以定制抛出的异常类型。

        ifPresent(Consumer<? super T> consumer) 在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。

        至此,全文结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值