10.6 新增的Optional方法

问题

用户希望执行以下操作:将 Optional 映射并展平到包含元素的流中;从几种可能的条件中进行选择;当元素存在时进行某种操作,否则返回默认值。

方案

使用 Java 9 为 Optional 类新增的 streamorifPresentOrElse 方法。

讨论

Java 8 引入了 Optional 类,用于告知客户端返回值可能合法为 null。返回的并非 null,而是空 Optional。对可能返回(或不返回)值的方法而言,Optional 是一种不错的包装器。

  • stream方法

例 10-28 显示了一种根据 ID 检索客户的查找器方法(finder method)。

例 10-28 根据 ID 查找客户

  1. public Optional<Customer> findById(int id) {
  2. return Optional.ofNullable(map.get(id));
  3. }

findById 方法假设客户包含在内存的 Map 中。map.get 方法在键存在时返回一个值,否则返回 null,因此将其作为 Optional.ofNullable 方法的参数时,要么在 Optional 中包装一个非空值,要么返回一个空 Optional

10.6 新增的Optional方法 - 图1 由于 Optional.of 方法在参数为 null 时将抛出异常,采用 Optional.ofNullable(arg) 更方便,其实现为 arg != null ? Optional.of(arg) : Optional.empty()

findById 方法返回的是 Optional,如果尝试返回客户集合则略显复杂。在 Java 8 中,不难写出如例 10-29 所示的代码。

例 10-29 在 Optional 上使用 filtermap 方法

  1. public Collection<Customer> findAllById(Integer... ids) {
  2. return Arrays.stream(ids)
  3. .map(this::findById)
  4. .filter(Optional::isPresent)
  5. .map(Optional::get)
  6. .collect(Collectors.toList());
  7. }

❶ 映射到 Stream>

❷ 筛掉所有空 Optional

❸ 调用 get 方法以映射到 Stream

上述实现并不难,但利用 Java 9 为 Optional 类新增的 stream 方法,可以使这个过程更简单。stream 方法的签名如下:

  1. Stream<T> stream()

如果值存在,stream 方法将返回一个顺序的单元素流,流中仅包含该值;如果值不存在,stream 方法将返回一个空流。借由 stream 方法,可选的客户流可以直接转换为客户流,如例 10-30 所示。

例 10-30 使用传入 Optional.streamflatMap 方法

  1. public Collection<Customer> findAllById(Integer... ids) {
  2. return Arrays.stream(ids)
  3. .map(this::findById)
  4. .flatMap(Optional::stream)
  5. .collect(Collectors.toList());
  6. }

❶ 映射到 Stream>

❷ 映射并展平为 Stream

使用 stream 纯粹是为了方便,但它不失为一种有用的方法。

  • or 方法

orElse 方法用于从 Optional 中提取值,它传入默认值作为参数:

  1. Customer customer = findById(id).orElse(Customer.DEFAULT)

也可以使用传入 Supplier 来创建默认值的 orElseGet 方法,不过这是一种成本较高的操作:

  1. Customer customer = findById(id).orElseGet(() -> createDefaultCustomer())

orElseorElseGet 方法均返回 Customer 实例。而 Java 9 新增的 or 方法可以在给定 Supplier 时返回 Optional,从而将查找客户的其他方式链接在一起。

or 方法的签名如下:

  1. Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

如果值存在,or 方法将返回描述该值的 Optional,否则返回由 Supplier 生成的 Optional

我们现在可以通过多种方式查找客户,例 10-31 展示了 or 方法的应用。

例 10-31 改用 or 方法

  1. public Optional<Customer> findById(int id) {
  2. return findByIdLocal(id)
  3. .or(() -> findByIdRemote(id))
  4. .or(() -> Optional.of(Customer.DEFAULT));
  5. }

程序首先在本地缓存中搜索客户,再访问远程服务器。如果两种方式都未能找到非空 Optional,最后一个子句创建一个默认值,将其包装在 Optional 中并返回。

  • ifPresentOrElse方法

如果 Optional 不为空,ifPresent 方法将执行 Consumer 指定的操作,如例 10-32 所示。

例 10-32 利用 ifPresent 方法仅打印非空客户

  1. public void printCustomer(Integer id) {
  2. findByIdLocal(id).ifPresent(System.out::println);
  3. }
  4.  
  5. public void printCustomers(Integer... ids) {
  6. Arrays.asList(ids)
  7. .forEach(this::printCustomer);
  8. }

➊ 仅打印非空 Optional

虽然 ifPresent 方法很有用,不过如果返回的 Optional 为空,我们可能还希望执行其他操作。Java 9 新增的 ifPresentOrElse 方法包括两个参数,在 Optional 为空时将执行第二个参数 Runnable 指定的操作。该方法的签名如下:

  1. void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

使用 ifPresentOrElse 方法时,只需提供一个不传入任何参数并返回 void 的 lambda 表达式,如例 10-33 所示。

例 10-33 打印客户或默认消息

  1. public void printCustomer(Integer id) {
  2. findByIdLocal(id).ifPresentOrElse(System.out::println,
  3. () -> System.out.println("Customer with id=" + id + " not found"));
  4. }

如果找到指定的客户,程序将打印该客户,否则打印默认消息。

Optional 类新增的这些方法均未从根本上改变各自的用途,但它们确实为开发提供了更大的便利。

另见

有关 Java 8 引入的 Optional 类请参见第 6 章。