11.2 Optional类入门

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

11.2 Optional类入门 - 图1

图 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的数据模型

  1. public class Person {
  2. private Optional<Car> car; ←---- 人可能有汽车,也可能没有汽车,因此将这个字段声明为Optional
  3. public Optional<Car> getCar() { return car; }
  4. }
  5. public class Car {
  6. private Optional<Insurance> insurance; ←---- 汽车可能进行了保险,也可能没有保险,所以将这个字段声明为Optional
  7. public Optional<Insurance> getInsurance() { return insurance; }
  8. }
  9. public class Insurance {
  10. private String name; ←---- 保险公司必须有名字
  11. public String getName() { return name; }
  12. }

发现Optional是如何丰富模型的语义了吧。代码中person引用的是Optional,而car引用的是Optional,这种方式非常清晰地表达了你的模型中一个person可能拥有也可能没有car的情形;同样,car可能进行了保险,也可能没有保险。

与此同时,我们看到insurance公司的名称被声明成String类型,而不是Optional,这非常清楚地表明声明为insurance公司的类型必须提供公司名称。使用这种方式,一旦解引用insurance公司名称时发生NullPointerException,你就能非常确定地知道出错的原因,不再需要为其添加null的检查,因为null的检查只会掩盖问题,并未真正地修复问题。insurance公司必须有个名称,所以,如果你遇到一个公司没有名称,你需要调查你的数据出了什么问题,而不应该再添加一段代码,将这个问题隐藏。

在你的代码中始终如一地使用Optional,能非常清晰地界定出变量值的缺失是结构上的问题,还是算法上的缺陷,抑或是数据中的问题。另外,我们还想特别强调,引入Optional类的意图并非要消除每一个null引用。与此相反,它的目标是帮助你更好地设计出普适的API,让程序员看到方法签名,就能了解它是否接受一个Optional的值。这种强制会让你更积极地将变量从Optional中解包出来,直面缺失的变量值。