使用内置的 null 来表示没有对象,每次使用引用的时候就必须测试一下引用是否为 null,这显得有点枯燥,而且势必会产生相当乏味的代码。
null 没啥行为,只会产生 NullPointException。
java.util.Optional为 null 值提供了一个轻量级代理,Optional 对象可以防止你的代码抛 NullPointException。
虽然 Optional 是 Java 8 为了支持流式编程才引入的,但其实它是一个通用的工具。实际上,在所有地方都使用 Optional 是没有意义的,有时候检查一下是不是 null 也挺好的,或者有时我们可以合理地假设不会出现 null,甚至有时候检查 NullPointException 异常也是可以接受的。
Optional 最有用武之地的是在那些“更接近数据”的地方,在问题空间中代表实体的对象上。
举个简单的例子,很多系统中都有 Person 类型,代码中有些情况下你可能没有一个实际的 Person 对象(或者可能有,但是你还没用关于那个人的所有信息)。这时,在传统方法下,你会用到一个 null 引用,并且在使用的时候测试它是不是 null。而现在,我们可以使用 Optional:
输出结果:
<Empty> Smith Bob Smith Bob Smith 11 Degree Lane, Frostbite Falls, MN
Person 的设计有时候叫“数据传输对象(DTO,data-transfer object)”。
所有字段都是 public final ,所以无 getter 和 setter 方法。即Person 不可变,只能通过构造器赋值,只能读而不能修改值。
想修改一个 Person,只能用一个新的 Person 对象来替换它。
empty 字段在对象创建的时候被赋值,用于快速判断这个 Person 对象是不是空对象。
想使用 Person,就必须使用 Optional 接口才能访问它的 String 字段,就不会意外触发 NPE。
可将 Person Optional 对象放在每个 Position 上:
class EmptyTitleException extends RuntimeException { } class Position { private String title; private Person person; Position(String jobTitle, Person employee) { setTitle(jobTitle); setPerson(employee); } Position(String jobTitle) { this(jobTitle, null); } public String getTitle() { return title; } public void setTitle(String newTitle) { // Throws EmptyTitleException if newTitle is null: title = Optional.ofNullable(newTitle) .orElseThrow(EmptyTitleException::new); } public Person getPerson() { return person; } public void setPerson(Person newPerson) { // Uses empty Person if newPerson is null: person = Optional.ofNullable(newPerson) .orElse(new Person()); } @Override public String toString() { return "Position: " + title + ", Employee: " + person; } public static void main(String[] args) { System.out.println(new Position("CEO")); System.out.println(new Position("Programmer", new Person("Arthur", "Fonzarelli"))); try { new Position(null); } catch (Exception e) { System.out.println("caught " + e); } } }
输出结果:
Position: CEO, Employee: <Empty> Position: Programmer, Employee: Arthur Fonzarelli caught EmptyTitleException
title 和 person 都是普通字段,修改唯一途径是调用 setTitle()、setPerson() ,都借助 Optional 对字段限制。
想保证 title 字段不会成 null,在 setTitle()检查参数值。但其实还有更好的做法,函数式编程一大优势就是可以让我们重用经过验证的功能,以减少自己手动编写代码可能产生的一些小错误。