目录
1、不使用Optional时的NullPointerException防御
厌烦的 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) 在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。
至此,全文结束。