6.2 从Optional中检索值

问题

用户希望从 Optional 中提取包含的值。

方案

如果确定 Optional 中存在值,则使用 get 方法,否则使用 orElse 方法的某种形式。如果只希望当值存在时才执行 Consumer,也可以使用 ifPresent 方法。

讨论

当调用一个返回 Optional 的方法时,可以通过调用 get 方法来检索其中包含的值。如果 Optional 为空,get 方法将抛出 NoSuchElementException

接下来,我们讨论如何从一个字符串流中返回第一个偶数长度的字符串,如例 6-4 所示。

例 6-4 检索第一个偶数长度的字符串

  1. Optional<String> firstEven =
  2. Stream.of("five", "even", "length", "string", "values")
  3. .filter(s -> s.length() % 2 == 0)
  4. .findFirst();

由于流中的所有字符串都无法通过筛选器,findFirst 方法将返回 Optional。可以通过在 Optional 上调用 get 方法来打印返回值:

  1. System.out.println(firstEven.get()) // 即便这条语句可以执行,也应避免使用

请注意,虽然上述语句可以执行,但除非确定 Optional 中包含值,否则不要调用 get 方法,以免程序抛出异常,如例 6-5 所示。

例 6-5 检索第一个奇数长度的字符串

  1. Optional<String> firstOdd =
  2. Stream.of("five", "even", "length", "string", "values")
  3. .filter(s -> s.length() % 2 != 0)
  4. .findFirst();
  5. System.out.println(firstOdd.get()); // 抛出NoSuchElementException

我们可以采用多种方案解决这个问题。例如,首先确定 Optional 中是否包含值,然后再检索,如例 6-6 所示。

例 6-6 检索第一个偶数长度的字符串(先确定是否包含值,再使用 get 方法)

  1. Optional<String> firstEven =
  2. Stream.of("five", "even", "length", "string", "values")
  3. .filter(s -> s.length() % 2 == 0)
  4. .findFirst();
  5. System.out.println(
  6. first.isPresent() ? first.get() : "No even length strings");

❶ 与例 6-4 所示的代码相同

❷ 仅当 isPresent 方法返回 true 时才调用 get 方法

不过,虽然上述代码可以执行,但只是添加了一个 isPresent 方法进行非空验证,程序并未有太大改进。

所幸我们还有一个更好的选择,这就是非常方便的 orElse 方法,如例 6-7 所示。

例 6-7 使用 orElse 方法

  1. Optional<String> firstOdd =
  2. Stream.of("five", "even", "length", "string", "values")
  3. .filter(s -> s.length() % 2 != 0)
  4. .findFirst();
  5. System.out.println(firstOdd.orElse("No odd length strings"));

如果包含的值存在,则 orElse 方法返回该值,否则返回提供的默认值。因此,如果已有回退值(fallback value),那么采用 orElse 方法是相当方便的。

orElse 方法包括三种形式。

  • 当包含的值存在时,orElse(T other) 将返回该值,否则返回指定的默认值 other
  • 当包含的值存在时,orElseGet(Supplier other) 将返回该值,否则调用 Supplier 并返回相应的结果。
  • 当包含的值存在时,orElseThrow(Supplier exceptionSupplier) 将返回该值,否则抛出由 Supplier 产生的异常。

orElseorElseGet 方法的不同之处在于,无论 Optional 中是否存在值,orElse 总会创建新字符串;而 orElseGet 使用 Supplier,仅在 Optional 为空时才执行。

如果值只是一个简单的字符串,那么 orElseorElseGet 方法的区别可以忽略不计;如果 orElse 方法的参数是一个复杂对象,那么传入 SupplierorElseGet 方法能确保仅在需要时才创建对象。相关示例如例 6-8 所示。

例 6-8 在 orElseGet 方法中使用 Supplier

  1. Optional<ComplexObject> val = values.stream.findFirst()
  2.  
  3. val.orElse(new ComplexObject());
  4. val.orElseGet(() -> new ComplexObject())

❶ 总是创建新对象

❷ 仅在需要时才创建对象

6.2 从Optional中检索值 - 图1 采用 Supplier 作为方法参数是延迟执行(deferred execution)或惰性执行(lazy execution)的一种应用,从而可以仅在需要时才在 Supplier 上调用 get 方法。3

3参见 Venkat Subramaniam 撰写的《Groovy 程序设计》一书,第 6 章详细讨论了这个问题。

在 Java 标准库中,orElseGet 方法的实现如例 6-9 所示。

例 6-9 orElseGet 方法的实现

  1. public T orElseGet(Supplier<? extends T> other) {
  2. return value != null ? value : other.get();
  3. }

valueOptionalT 类型的最终特性

orElseThrow 方法同样传入 Supplier,该方法的签名如下:

  1. <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)

因此,当 Optional 包含值时,不会执行用作 Supplier 参数的构造函数引用,如例 6-10 所示。

例 6-10 采用 orElseThrow 作为 Supplier

  1. Optional<String> first =
  2. Stream.of("five", "even", "length", "string", "values")
  3. .filter(s -> s.length() % 2 == 0)
  4. .findFirst();
  5.  
  6. System.out.println(first.orElseThrow(NoSuchElementException::new));

此外,ifPresent 方法支持提供一个仅当 Optional 包含值时才执行的 Consumer,如例 6-11 所示。

例 6-11 ifPresent 方法的应用

  1. Optional<String> first =
  2. Stream.of("five", "even", "length", "string", "values")
  3. .filter(s -> s.length() % 2 == 0)
  4. .findFirst();
  5. first.ifPresent(val -> System.out.println("Found an even-length string"));
  6. first = Stream.of("five", "even", "length", "string", "values")
  7. .filter(s -> s.length() % 2 != 0)
  8. .findFirst();
  9. first.ifPresent(val -> System.out.println("Found an odd-length string"));

执行上述程序将仅打印“Found an even-length string”消息。

另见

有关 Supplier 接口的讨论请参见范例 2.2,有关构造函数引用的讨论请参见范例 1.3,有关返回 OptionalfindAnyfindFirst 方法请参见范例 3.9。