6.2 从Optional中检索值
问题
用户希望从 Optional 中提取包含的值。
方案
如果确定 Optional 中存在值,则使用 get 方法,否则使用 orElse 方法的某种形式。如果只希望当值存在时才执行 Consumer,也可以使用 ifPresent 方法。
讨论
当调用一个返回 Optional 的方法时,可以通过调用 get 方法来检索其中包含的值。如果 Optional 为空,get 方法将抛出 NoSuchElementException。
接下来,我们讨论如何从一个字符串流中返回第一个偶数长度的字符串,如例 6-4 所示。
例 6-4 检索第一个偶数长度的字符串
Optional<String> firstEven =Stream.of("five", "even", "length", "string", "values").filter(s -> s.length() % 2 == 0).findFirst();
由于流中的所有字符串都无法通过筛选器,findFirst 方法将返回 Optional。可以通过在 Optional 上调用 get 方法来打印返回值:
System.out.println(firstEven.get()) // 即便这条语句可以执行,也应避免使用
请注意,虽然上述语句可以执行,但除非确定 Optional 中包含值,否则不要调用 get 方法,以免程序抛出异常,如例 6-5 所示。
例 6-5 检索第一个奇数长度的字符串
Optional<String> firstOdd =Stream.of("five", "even", "length", "string", "values").filter(s -> s.length() % 2 != 0).findFirst();System.out.println(firstOdd.get()); // 抛出NoSuchElementException
我们可以采用多种方案解决这个问题。例如,首先确定 Optional 中是否包含值,然后再检索,如例 6-6 所示。
例 6-6 检索第一个偶数长度的字符串(先确定是否包含值,再使用
get方法)
Optional<String> firstEven = ➊Stream.of("five", "even", "length", "string", "values").filter(s -> s.length() % 2 == 0).findFirst();System.out.println(first.isPresent() ? first.get() : "No even length strings"); ➋
❶ 与例 6-4 所示的代码相同
❷ 仅当 isPresent 方法返回 true 时才调用 get 方法
不过,虽然上述代码可以执行,但只是添加了一个 isPresent 方法进行非空验证,程序并未有太大改进。
所幸我们还有一个更好的选择,这就是非常方便的 orElse 方法,如例 6-7 所示。
例 6-7 使用
orElse方法
Optional<String> firstOdd =Stream.of("five", "even", "length", "string", "values").filter(s -> s.length() % 2 != 0).findFirst();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产生的异常。
orElse 与 orElseGet 方法的不同之处在于,无论 Optional 中是否存在值,orElse 总会创建新字符串;而 orElseGet 使用 Supplier,仅在 Optional 为空时才执行。
如果值只是一个简单的字符串,那么 orElse 与 orElseGet 方法的区别可以忽略不计;如果 orElse 方法的参数是一个复杂对象,那么传入 Supplier 的 orElseGet 方法能确保仅在需要时才创建对象。相关示例如例 6-8 所示。
例 6-8 在
orElseGet方法中使用Supplier
- Optional<ComplexObject> val = values.stream.findFirst()
- val.orElse(new ComplexObject()); ➊
- val.orElseGet(() -> new ComplexObject()) ➋
❶ 总是创建新对象
❷ 仅在需要时才创建对象
采用
Supplier作为方法参数是延迟执行(deferred execution)或惰性执行(lazy execution)的一种应用,从而可以仅在需要时才在Supplier上调用get方法。3
3参见 Venkat Subramaniam 撰写的《Groovy 程序设计》一书,第 6 章详细讨论了这个问题。
在 Java 标准库中,orElseGet 方法的实现如例 6-9 所示。
例 6-9
orElseGet方法的实现
- public T orElseGet(Supplier<? extends T> other) {
- return value != null ? value : other.get(); ➊
- }
➊ value 是 Optional 中 T 类型的最终特性
orElseThrow 方法同样传入 Supplier,该方法的签名如下:
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
因此,当 Optional 包含值时,不会执行用作 Supplier 参数的构造函数引用,如例 6-10 所示。
例 6-10 采用
orElseThrow作为Supplier
- Optional<String> first =
- Stream.of("five", "even", "length", "string", "values")
- .filter(s -> s.length() % 2 == 0)
- .findFirst();
- System.out.println(first.orElseThrow(NoSuchElementException::new));
此外,ifPresent 方法支持提供一个仅当 Optional 包含值时才执行的 Consumer,如例 6-11 所示。
例 6-11
ifPresent方法的应用
Optional<String> first =Stream.of("five", "even", "length", "string", "values").filter(s -> s.length() % 2 == 0).findFirst();first.ifPresent(val -> System.out.println("Found an even-length string"));first = Stream.of("five", "even", "length", "string", "values").filter(s -> s.length() % 2 != 0).findFirst();first.ifPresent(val -> System.out.println("Found an odd-length string"));
执行上述程序将仅打印“Found an even-length string”消息。
另见
有关 Supplier 接口的讨论请参见范例 2.2,有关构造函数引用的讨论请参见范例 1.3,有关返回 Optional 的 findAny 和 findFirst 方法请参见范例 3.9。
采用 