Java 从Optional中检索值,用户希望从 Optional
中提取包含的值,如果确定 Optional
中存在值,则使用 get
方法,否则使用 orElse
方法的某种形式。如果只希望当值存在时才执行 Consumer
,也可以使用 ifPresent
方法。
Java 从Optional中检索值 问题描述
用户希望从 Optional
中提取包含的值。
Java 从Optional中检索值 解决方案
如果确定 Optional
中存在值,则使用 get
方法,否则使用 orElse
方法的某种形式。如果只希望当值存在时才执行 Consumer
,也可以使用 ifPresent
方法。
Java 从Optional中检索值 具体实例
当调用一个返回 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<String>
。可以通过在 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<? extends T> other)
将返回该值,否则调用Supplier
并返回相应的结果。 - 当包含的值存在时,
orElseThrow(Supplier<? extends X> 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 程序设计》一书,Optional的创建详细讨论了这个问题。
在 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”消息。