本文参考书籍《Java 8实战》,陆明刚、劳佳 译,如有侵权,请联系删除!
如何为缺失的值建模
假设你需要处理下面这样的嵌套对象,这是一个拥有汽车及汽车保险的客户。
public class Person {
private Car car;
public Car getCar() { return car; }
}
public class Car {
private Insurance insurance;
public Insurance getInsurance() { return insurance; }
}
public class Insurance {
private String name;
public String getName() { return name; }
}
那么,下面这段代码存在怎样的问题呢?
public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
}
很明显,如果一个人没有车(person.getCar()返回null),或者他的车没有保险(person.getCar().getInsurance()返回null),这段代码将会抛出NullPointerException。怎样做才能避免这种不期而至的NullPointerException呢?通常,可以在需要的地方添加null的检查,下面这个例子是我们试图在方法中避免NullPointerException的第一次尝试。
public String getCarInsuranceName(Person person) {
if (person != null) {
Car car = person.getCar();
if (car != null) {
Insurance insurance = car.getInsurance();
if (insurance != null) {
return insurance.getName();
}
}
}
return "Unknown";
}
这种方式不具备扩展性,同时还牺牲了代码的可读性。下面的代码清单中,我们试图通过一种不同的方式避免这种问题。
public String getCarInsuranceName(Person person) {
if (person == null) {
return "Unknown";
}
Car car = person.getCar();
if (car == null) {
return "Unknown";
}
Insurance insurance = car.getInsurance();
if (insurance == null) {
return "Unknown";
}
return insurance.getName();
}
这种方案远非理想,现在这个方法有了四个截然不同的退出点,使得代码的维护异常艰难。我们需要更优雅的方式来对缺失的变量值建模。
Optional 类入门
Java 8中引入了一个新的类java.util.Optional<T>,这是一个封装Optional值的类。变量存在时, Optional类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的Optional对象,由方法Optional.empty()返回。Optional.empty()方法是一个静态工厂方法,它返回Optional类的特定单一实例。你可能还有疑惑,null引用和Optional.empty()有什么本质的区别吗?从语义上,你可以把它们当作一回事儿,但是实际中它们之间的差别非常大:如 果 你 尝 试 解 引 用 一 个 null , 一 定 会 触 发 NullPointerException , 不 过 使 用Optional.empty()就完全没事儿,它是Optional类的一个有效对象,多种场景都能调用。使用Optional类意味着,如果你知道一个人可能有也可能没有车,那么Person类内部的car变量就不应该声明为Car,而是应该将其声明为Optional<Car>类型。如下:
public class Person {
private Optional<Car> car;
public Optional<Car> getCar() {
return car;
}
}
public class Car {
private Optional<Insurance> insurance;
public Optional<Insurance> getInsurance() {
return insurance;
}
}
public class Insurance {
private String name;
public String getName() {
return name;
}
}
代码中person引用的是Optional<Car>,而car引用的是Optional<Insurance>,这种方式非常清晰地表达了你的模型中一个person可能拥有也可能没有car的情形,同样, car可能进行了保险,也可能没有保险。注意,insurance公司的名称被声明成String类型,而不是Optional-<String>,这非常清楚地表明声明为insurance公司的类型必须提供公司名称。使用这种方式,一旦解引用insurance公司名称时发生NullPointerException,你就能非常确定地知道出错的原因,不再需要为其添加null的检查,因为null的检查只会掩盖问题,并未真正地修复问题。insurance公司必须有个名字,所以,如果你遇到一个公司没有名称,你需要调查你的数据出了什么问题,而不应该再添加一段代码,将这个问题隐藏。
创建 Optional 对象
1、声明一个空的Optional
可以通过静态工厂方法Optional.empty,创建一个空的Optional对象:
Optional<Car> optCar = Optional.empty();
2、依据一个非空值创建Optional
可以使用静态工厂方法Optional.of,依据一个非空值创建一个Optional对象:
Optional<Car> optCar = Optional.of(car);
如果car为null,这段代码会立即抛出一个NullPointerException,而不是等到你试图访问car的属性值时才返回一个错误。
3. 可接受null的Optional
使用静态工厂方法Optional.ofNullable,你可以创建一个允许null值的Optional对象:
Optional<Car> optCar = Optional.ofNullable(car);
如果car为null,那么得到的Optional对象就是个空对象。
使用Optional对象
Optional类提供了多种方法读取Optional实例中的变量值。
1、get()
get()是这些方法中最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException异常。
2、orElse(T other)
如果Optional对象不包含值,那么将返回一个默认值。
3、orElseGet(Supplier<? extends T> other)
如果Optional对象不包含值,那么将使用Supplier获取一个对象。
4、orElseThrow(Supplier<? extends X> exceptionSupplier)
和get方法非常类似,如果Optional对象不包含值都会抛出一个异常,但是使用orElseThrow可以定制希望抛出的异常类型。
5、ifPresent(Consumer<? super T>)
ifPresent(Consumer<? super T>)能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。
由于Optional类设计时就没特别考虑将其作为类的字段使用,所以它也并未实现Serializable接口。由于这个原因,如果你的应用使用了某些要求序列化的库或者框架,在域模型中使用Optional,有可能引发应用程序故障。