6.5 Optional的映射
问题
用户希望将函数应用到 Optional 实例的集合,但前提是 Optional 实例包含值。
方案
使用 Optional 类定义的 map 方法。
讨论
假设存在一个包含员工 ID 值的列表,我们希望检索相应的员工实例集合。如果 findEmployeeById 方法具有以下签名,则搜索所有员工后将返回一个 Optional 实例的集合,其中某些实例可能为空。例 6-20 显示了如何筛掉为空的 Optional。
- public Optional<Employee> findEmployeeById(int id)
例 6-20 根据 ID 查找
Employee(使用Stream.map方法)
- public List<Employee> findEmployeesByIds(List<Integer> ids) {
- return ids.stream()
- .map(this::findEmployeeById) ➊
- .filter(Optional::isPresent)) ➋
- .map(Optional::get) ➌
- .collect(Collectors.toList());
- }
❶ Stream
❷ 删除空的 Optional
❸ 检索确定存在的值
如上所示,第一个 map 操作的结果是一个由 Optional 构成的流,每个 Optional 要么包含一名员工,要么为空。为提取包含的值,我们很自然会想到调用 get 方法。但必须注意,除非确定值存在,否则不要调用 get 方法。为避免出现错误,我们采用 filter 方法,它传入 Optional::isPresent 作为谓词以删除所有空 Optional,然后通过第二个 map 操作(传入 Optional::get)将各个 Optional 映射到它们所包含的值。
本例使用了 Stream 接口定义的 map 方法,而 Optional 类同样定义有 map 方法,其签名为:
- <U> Optional<U> map(Function<? super T,? extends U> mapper)
Optional.map 方法传入 Function 作为参数。如果 Optional 不为空,map 方法将提取包含的值并应用给定的函数,然后返回一个包含结果的 Optional;如果 Optional 为空,map 方法将返回一个空的 Optional。
我们使用 Optional.map 方法重写例 6-20 的查找操作,结果如例 6-21 所示。
例 6-21 根据 ID 查找
Employee(使用Optional.map方法)
- public List<Employee> findEmployeesByIds(List<Integer> ids) {
- return ids.stream()
- .map(this::findEmployeeById) ➊
- .flatMap(optional ->
- optional.map(Stream::of) ➋
- .orElseGet(Stream::empty)) ➌
- .collect(Collectors.toList());
- }
❶ Stream
❷ 将非空 Optional 转换为 Optional
❸ 从 Optional 中提取 Stream
如果包含员工的 Optional 不为空,则在包含的值上调用 Stream::of,将其转换为该值的一个单元素流(one-element stream)并包装在 Optional 中,否则返回一个空的 Optional。
在本例中,如果根据某个员工 ID 找到了相应的员工,那么 findEmployeeById 方法将返回该值的 Optional,optional.map(Stream::of) 方法随后返回一个 Optional,它包含存储该员工信息的单元素流,由此得到 Optional。接下来,orElseGet 方法将包含的值提取出来,生成 Stream。
如果 findEmployeeById 方法返回为空的 Optional,optional.map(Stream::of) 方法同样将返回一个空的 Optional,而 orElseGet(Stream::empty) 方法将返回一个空的流。
由此得到 Stream 元素与空流的组合,Stream.flatMap 方法的真正用途就在于此。仅对非空流而言,Stream.flatMap 方法将所有内容简化为 Stream,因此 collect 方法可以将非空流作为员工列表返回。
相应的过程如图 6-1 所示。

图 6-1:Optional.map 和 Optional.flatMap 操作
Optional.map 是一种或许有助于简化流处理代码的便捷 5 方法。对不熟悉 flatMap 操作的开发人员而言,之前讨论的 filter/map 方案显然更为直观,且得到的结果并无二致。
5至少其设计思路如此。
当然,我们可以在 Optional.map 方法中使用任何所需的函数。Javadoc 详细描述了如何将名称转换为文件输入流,其他应用请参见范例 6.4。
此外,Java 9 为 Optional 类引入了一个名为 stream 的新方法。如果 Optional 不为空,stream 方法将返回一个包装包含值的单元素流,否则返回一个空流。详见范例 10.6。
另见
有关 Optional 在 DAO 层中的应用请参见范例 6.3,有关 Stream.flatMap 方法的讨论请参见范例 3.11,有关 Optional.flatMap 方法的讨论请参见范例 6.4,有关 Java 9 为 Optional 类引入的新方法请参见范例 10.6。
