所以用 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,你永远不需要它)所言,在初稿时“实现尽最大可能的简单”,直到程序在某些方面要求你为其添加一些额外的特性,而不是假设这是必要的。
虽然使用了 Optional,可以免受 NullPointerExceptions,但 Staff 类对此毫不知情。
// typeinfo/Staff.java import java.util.*; public class Staff extends ArrayList<Position> { public void add(String title, Person person) { add(new Position(title, person)); } public void add(String... titles) { for (String title : titles) add(new Position(title)); } public Staff(String... titles) { add(titles); } public Boolean positionAvailable(String title) { for (Position position : this) if (position.getTitle().equals(title) && position.getPerson().empty) return true; return false; } public void fillPosition(String title, Person hire) { for (Position position : this) if (position.getTitle().equals(title) && position.getPerson().empty) { position.setPerson(hire); return; } throw new RuntimeException( "Position " + title + " not available"); } public static void main(String[] args) { Staff staff = new Staff("President", "CTO", "Marketing Manager", "Product Manager", "Project Lead", "Software Engineer", "Software Engineer", "Software Engineer", "Software Engineer", "Test Engineer", "Technical Writer"); staff.fillPosition("President", new Person("Me", "Last", "The Top, Lonely At")); staff.fillPosition("Project Lead", new Person("Janet", "Planner", "The Burbs")); if (staff.positionAvailable("Software Engineer")) staff.fillPosition("Software Engineer", new Person( "Bob", "Coder", "Bright Light City")); System.out.println(staff); } }
输出结果:
[Position: President, Employee: Me Last The Top, Lonely At, Position: CTO, Employee: <Empty>, Position: Marketing Manager, Employee: <Empty>, Position: Product Manager, Employee: <Empty>, Position: Project Lead, Employee: Janet Planner The Burbs, Position: Software Engineer, Employee: Bob Coder Bright Light City, Position: Software Engineer, Employee: <Empty>, Position: Software Engineer, Employee: <Empty>, Position: Software Engineer, Employee: <Empty>, Position: Test Engineer, Employee: <Empty>, Position: Technical Writer, Employee: <Empty>]
有些地方你可能还是要测试引用是不是 Optional
,这跟检查是否为 null
没什么不同。但是在其它地方(例如本例中的 toString()
转换),你就不必执行额外的测试了,而可以直接假设所有对象都是有效的。
标记接口
有时使用标记接口表示空值更方便,把它的名字当做标签来用即可
用接口取代具体类,即可使用 DynamicProxy
自动创建 Null
对象。
假设有一个 Robot
接口
Operation
包含一个描述和一个命令(这用到了命令模式)。
定义成函数式接口的引用,所以可以把 lambda 表达式或者方法的引用传给 Operation
的构造器:
现在我们可以创建一个扫雪 Robot
:
假设许多不同类型的 Robot
,想让每种 Robot
都创建一个 Null
对象来执行一些特殊的操作
本例中,提供 Null
对象所代表 Robot
的确切类型信息。这些信息是通过动态代理捕获的:
如果你需要一个空 Robot
对象,只需调用 newNullRobot()
,并传递需要代理的 Robot
类型。这个代理满足了 Robot
和 Null
接口的需要,并提供了它所代理的类型的确切名字。