11.2 Optional类入门
汲取Haskell和Scala的灵感,Java 8中引入了一个新的类java.util.Optional。这是一个封装Optional值的类。举例来说,使用新的类意味着,如果你知道一个人可能有也可能没有汽车,那么Person类内部的car变量就不应该声明为Car,遭遇某人没有汽车时把null引用赋值给它,而是应该像图11-1那样直接将其声明为Optional类型。

图 11-1 使用Optional定义的Car类
变量存在时,Optional类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的Optional对象,由方法Optional.empty()返回。Optional.empty()方法是一个静态工厂方法,它返回Optional类的特定单一实例。你可能还有疑惑,null引用和Optional.empty()有什么本质的区别吗?从语义上讲,你可以把它们当作一回事儿,但是实际中它们之间的差别非常大:如果你尝试解引用一个null,那么一定会触发NullPointerException,不过使用Optional.empty()就完全没事儿,它是Optional类的一个有效对象,多种场景都能调用,非常有用。关于这一点,接下来的部分会详细介绍。
使用Optional而不是null的一个非常重要而又实际的语义区别是,第一个例子中,我们在声明变量时使用的是Optional类型,而不是Car类型,这句声明非常清楚地表明了这里发生变量缺失是允许的。与此相反,使用Car这样的类型,可能将变量赋值为null,这意味着你需要独立面对这些,你只能依赖你对业务模型的理解,判断一个null是否属于该变量的有效范畴。
牢记上面这些原则,你现在可以使用Optional类对代码清单11-1中最初的代码进行重构,结果如下。
代码清单 11-4 使用
Optional重新定义Person/Car/Insurance的数据模型
public class Person {private Optional<Car> car; ←---- 人可能有汽车,也可能没有汽车,因此将这个字段声明为Optionalpublic Optional<Car> getCar() { return car; }}public class Car {private Optional<Insurance> insurance; ←---- 汽车可能进行了保险,也可能没有保险,所以将这个字段声明为Optionalpublic Optional<Insurance> getInsurance() { return insurance; }}public class Insurance {private String name; ←---- 保险公司必须有名字public String getName() { return name; }}
发现Optional是如何丰富模型的语义了吧。代码中person引用的是Optional,而car引用的是Optional,这种方式非常清晰地表达了你的模型中一个person可能拥有也可能没有car的情形;同样,car可能进行了保险,也可能没有保险。
与此同时,我们看到insurance公司的名称被声明成String类型,而不是Optional,这非常清楚地表明声明为insurance公司的类型必须提供公司名称。使用这种方式,一旦解引用insurance公司名称时发生NullPointerException,你就能非常确定地知道出错的原因,不再需要为其添加null的检查,因为null的检查只会掩盖问题,并未真正地修复问题。insurance公司必须有个名称,所以,如果你遇到一个公司没有名称,你需要调查你的数据出了什么问题,而不应该再添加一段代码,将这个问题隐藏。
在你的代码中始终如一地使用Optional,能非常清晰地界定出变量值的缺失是结构上的问题,还是算法上的缺陷,抑或是数据中的问题。另外,我们还想特别强调,引入Optional类的意图并非要消除每一个null引用。与此相反,它的目标是帮助你更好地设计出普适的API,让程序员看到方法签名,就能了解它是否接受一个Optional的值。这种强制会让你更积极地将变量从Optional中解包出来,直面缺失的变量值。
