使用内置的 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()
检查参数值。但其实还有更好的做法,函数式编程一大优势就是可以让我们重用经过验证的功能,以减少自己手动编写代码可能产生的一些小错误。
- 所以用
ofNullable()
把newTitle
转换一个Optional
传null
,ofNullable()
返回Optional.empty()
。 - 调用
orElseThrow()
如果newTitle
的值是null
,会得到异常。
这里我们并没有把title
保存成Optional
,但通过应用Optional
的功能,我们仍对字段加了约束。
在这个方案里边,你仍然可能会得到一个异常。不同的是,错误产生那刻(向setTitle()
传null
值时)就抛异常,而不发生在其它时刻。使用EmptyTitleException
有助于定位 BUG。
Person
字段的限制:如果把值设 null
,程序会自动把将它赋值成一个空的 Person
对象。先前我们也用过类似的方法把字段转换成 Option
,但这里我们是在返回结果的时候使用 orElse(new Person())
插入一个空的 Person
对象替代了 null
。
在 Position
里,没有创建一个表示“空”的标志位或者方法,因为 person
字段的 Person
对象为空,就表示这个 Position
是个空位置。之后,你可能会发现你必须添加一个显式的表示“空位”的方法,但是正如 YAGNI (You Aren't Going to Need It,你永远不需要它)所言,在初稿时“实现尽最大可能的简单”,直到程序在某些方面要求你为其添加一些额外的特性,而不是假设这是必要的。
全部评论
(0) 回帖