Java8实战-总结41
- 用Optional取代null
- Optional 类入门
- 应用 Optional 的几种模式
- 创建 Optional 对象
- 使用 map 从 Optional 对象中提取和转换值
用Optional取代null
Optional 类入门
Java 8
中引入了一个新的类java.util.Optional<T>
。这是一个封装Optional
值的类。举例来说,使用新的类意味着,如果你知道一个人可能有也可能没有车,那么Person
类内部的car
变量就不应该声明为Car
,遭遇某人没有车时把null
引用赋值给它,而是应该像下图直接将其声明为Optional<Car>
类型。
变量存在时,Optional
类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的Optional
对象,由方法Optional.empty()
返回。Optional.empty()
方法是一个静态工厂方法,它返回Optional
类的特定单一实例。null
引用和Optional.empty()
有什么本质的区别吗?从语义上,可以把它们当作一回事儿,但是实际中它们之间的差别非常大:如果尝试解引用一个 null
,一定会触发 NullPointerException
,不过使用Optional.empty()
就完全没事儿,它是Optional
类的一个有效对象,多种场景都能调用,非常有用。
使用Optional
而不是null
的一个非常重要而又实际的语义区别是,第一个例子中,在声明变量时使用的是Optional<Car>
类型,而不是Car
类型,这句声明非常清楚地表明了这里发生变量缺失是允许的。与此相反,使用Car
这样的类型,可能将变量赋值为null
,这意味着需要独立面对这些,只能依赖对业务模型的理解,判断一个null
是否属于该变量的有效范畴。
牢记上面这些原则,现在可以使用Optional
类对最初的代码进行重构,结果如下:
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
,能非常清晰地界定出变量值的缺失是结构上的问题,还是算法上的缺陷,抑或是数据中的问题。另外,引入Optional
类的意图并非要消除每一个null
引用。与此相反,它的目标是帮助更好地设计出普适的API
,让程序员看到方法签名,就能了解它是否接受一个Optional
的值。这种强制可以帮助更积极地将变量从Optional
中解包出来,直面缺失的变量值。
应用 Optional 的几种模式
创建 Optional 对象
使用Optional
之前,首先需要学习的是如何创建Optional
对象。完成这一任务有多种方法。
- 声明一个空的Optional
可以通过静态工厂方法Optional.empty
,创建一个空的Optional
对象:
Optional<Car> optCar = Optional.empty();
- 依据一个非空值创建Optional
还可以使用静态工厂方法Optional.of
,依据一个非空值创建一个Optional
对象:
Optional<Car> optCar = Optional.of(car);
如果car
是一个null
,这段代码会立即抛出一个NullPointerException
,而不是等到试图访问car
的属性值时才返回一个错误。
- 可接受null的Optional
最后,使用静态工厂方法Optional.ofNullable
,可以创建一个允许null
值的Optional
对象:
Optional<Car> optCar = Optional.ofNullable(car);
如果car
是null
,那么得到的Optional
对象就是个空对象。
还需要获取Optional
变量中的值,Optional
提供了一个get
方法,它能非常精准地完成这项工作,不过get
方法在遭遇到空的Optional
对象时也会抛出异常,所以不按照约定的方式使用它,又会让我们再度陷入由null
引起的代码维护的梦魇。因此,首先从无需显式检查的Optional
值的使用入手,这些方法与Stream
中的某些操作极其相似。
使用 map 从 Optional 对象中提取和转换值
从对象中提取信息是一种比较常见的模式。比如,可能想要从insurance
公司对象中提取公司的名称。提取名称之前,需要检查insurance
对象是否为null
,代码如下所示:
String name = null;
if(insurance != null){
name = insurance.getName();
}
为了支持这种模式,Optional
提供了一个map
方法。它的工作方式如下:
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
从概念上,这与流的map
方法相差无几。map
操作会将提供的函数应用于流的每个元素。可以把Optional
对象看成一种特殊的集合数据,它至多包含一个元素。如果Optional
包含一个值,那函数就将该值作为参数传递给map
,对该值进行转换。如果Optional
为空,就什么也不做。下图对这种相似性进行了说明,展示了把一个将正方形转换为三角形的函数,分别传递给正方形和Optional
正方形流的map
方法之后的结果。
这看起来挺有用,但是怎样才能应用起来,重构之前的代码呢?前文的代码里用安全的方式链接了多个方法。
public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
}