在我们学习终端操作(Terminal Operations)之前,我们必须考虑在一个空流中获取元素会发生什么。我们喜欢沿着“快乐路径”把流连接起来,同时假设流不会中断。然而,在流中放置 null
却会轻易令其中断。那么是否存在某种对象,可以在持有流元素的同时,即使在我们查找的元素不存在时,也能友好地对我们进行提示(也就是说,不会产生异常)?
Optional 可以实现这样的功能。一些标准流操作返回 Optional 对象,因为它们并不能保证预期结果一定存在。包括:
findFirst()
返回一个包含第一个元素的 Optional 对象,如果流为空则返回 Optional.emptyfindAny()
返回包含任意元素的 Optional 对象,如果流为空则返回 Optional.emptymax()
和 min()
返回一个包含最大值或者最小值的 Optional 对象,如果流为空则返回 Optional.emptyreduce()
不再以 identity
形式开头,而是将其返回值包装在 Optional 中。(identity
对象成为其他形式的 reduce()
的默认结果,因此不存在空结果的风险)
对于数字流 IntStream、LongStream 和 DoubleStream,average()
会将结果包装在 Optional 以防止流为空。
以下是对空流进行所有这些操作的简单测试:
import java.util.stream.*;
class OptionalsFromEmptyStreams {
public static void main(String[] args) {
System.out.println(Stream.<String>empty()
.findFirst());
System.out.println(Stream.<String>empty()
.findAny());
System.out.println(Stream.<String>empty()
.max(String.CASE_INSENSITIVE_ORDER));
System.out.println(Stream.<String>empty()
.min(String.CASE_INSENSITIVE_ORDER));
System.out.println(Stream.<String>empty()
.reduce((s1, s2) -> s1 + s2));
System.out.println(IntStream.empty()
.average());
}
}
输出结果:
当流为空的时候你会获得一个 Optional.empty 对象,而不是抛出异常。Optional 拥有 toString()
方法可以用于展示有用信息。
注意,空流是通过 Stream.
创建的。如果你在没有任何上下文环境的情况下调用 Stream.empty()
,Java 并不知道它的数据类型;这个语法解决了这个问题。如果编译器拥有了足够的上下文信息,比如:
Stream<String> s = Stream.empty();
就可以在调用 empty()
时推断类型。
这个示例展示了 Optional 的两个基本用法:
import java.util.*;
import java.util.stream.*;
class OptionalBasics {
static void test(Optional<String> optString) {
if (optString.isPresent()) {
System.out.println(optString.get());
} else {
System.out.println("Nothing inside!");
}
}
public static void main(String[] args) {
test(Stream.of("Epithets").findFirst());
test(Stream.<String>empty().findFirst());
}
}
输出结果:
当你接收到 Optional 对象时,应首先调用 isPresent()
检查其中是否包含元素。如果存在,可使用 get()
获取。
有许多便利函数可以解包 Optional ,这简化了上述“对所包含的对象的检查和执行操作”的过程:
ifPresent(Consumer)
:当值存在时调用 Consumer,否则什么也不做。orElse(otherObject)
:如果值存在则直接返回,否则生成 otherObject。orElseGet(Supplier)
:如果值存在则直接返回,否则使用 Supplier 函数生成一个可替代对象。orElseThrow(Supplier)
:如果值存在直接返回,否则使用 Supplier 函数生成一个异常。如下是针对不同便利函数的简单演示:
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class Optionals {
static void basics(Optional<String> optString) {
if (optString.isPresent()) {
System.out.println(optString.get());
} else {
System.out.println("Nothing inside!");
}
}
static void ifPresent(Optional<String> optString) {
optString.ifPresent(System.out::println);
}
static void orElse(Optional<String> optString) {
System.out.println(optString.orElse("Nada"));
}
static void orElseGet(Optional<String> optString) {
System.out.println(
optString.orElseGet(() -> "Generated"));
}
static void orElseThrow(Optional<String> optString) {
try {
System.out.println(optString.orElseThrow(
() -> new Exception("Supplied")));
} catch (Exception e) {
System.out.println("Caught " + e);
}
}
static void test(String testName, Consumer<Optional<String>> cos) {
System.out.println(" === " + testName + " === ");
cos.accept(Stream.of("Epithets").findFirst());
cos.accept(Stream.<String>empty().findFirst());
}
public static void main(String[] args) {
test("basics", Optionals::basics);
test("ifPresent", Optionals::ifPresent);
test("orElse", Optionals::orElse);
test("orElseGet", Optionals::orElseGet);
test("orElseThrow", Optionals::orElseThrow);
}
}
输出结果:
test()
通过传入所有方法都适用的 Consumer 来避免重复代码。
orElseThrow()
通过 catch 关键字来捕获抛出的异常。
当我们在自己的代码中加入 Optional 时,可以使用下面 3 个静态方法:
empty()
:生成一个空 Optional。of(value)
:将一个非空值包装到 Optional 里。ofNullable(value)
:针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中。下面来看看它是如何工作的。代码示例:
import java.util.*;
class CreatingOptionals {
static void test(String testName, Optional<String> opt) {
System.out.println(" === " + testName + " === ");
System.out.println(opt.orElse("Null"));
}
public static void main(String[] args) {
test("empty", Optional.empty());
test("of", Optional.of("Howdy"));
try {
test("of", Optional.of(null));
} catch (Exception e) {
System.out.println(e);
}
test("ofNullable", Optional.ofNullable("Hi"));
test("ofNullable", Optional.ofNullable(null));
}
}
输出结果:
我们不能通过传递 null
到 of()
来创建 Optional
对象。最安全的方法是, 使用 ofNullable()
来优雅地处理 null
。
当我们的流管道生成了 Optional 对象,下面 3 个方法可使得 Optional 的后续能做更多的操作:
filter(Predicate)
:对 Optional 中的内容应用Predicate 并将结果返回。如果 Optional 不满足 Predicate ,将 Optional 转化为空 Optional 。如果 Optional 已经为空,则直接返回空Optional 。map(Function)
:如果 Optional 不为空,应用 Function 于 Optional 中的内容,并返回结果。否则直接返回 Optional.empty。flatMap(Function)
:同 map()
,但是提供的映射函数将结果包装在 Optional 对象中,因此 flatMap()
不会在最后进行任何包装。以上方法都不适用于数值型 Optional。一般来说,流的 filter()
会在 Predicate 返回 false
时移除流元素。而 Optional.filter()
在失败时不会删除 Optional,而是将其保留下来,并转化为空。下面请看代码示例:
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
class OptionalFilter {
static String[] elements = {
"Foo", "", "Bar", "Baz", "Bingo"
};
static Stream<String> testStream() {
return Arrays.stream(elements);
}
static void test(String descr, Predicate<String> pred) {
System.out.println(" ---( " + descr + " )---");
for (int i = 0; i <= elements.length; i++) {
System.out.println(
testStream()
.skip(i)
.findFirst()
.filter(pred));
}
}
public static void main(String[] args) {
test("true", str -> true);
test("false", str -> false);
test("str != \"\"", str -> str != "");
test("str.length() == 3", str -> str.length() == 3);
test("startsWith(\"B\")",
str -> str.startsWith("B"));
}
}
输出结果:
即使输出看起来像流,要特别注意 test()
中的 for 循环。每一次的for循环都重新启动流,然后跳过for循环索引指定的数量的元素,这就是流只剩后续元素的原因。然后调用findFirst()
获取剩余元素中的第一个元素,并包装在一个 Optional
对象中。
注意,不同于普通 for 循环,这里的索引值范围并不是 i < elements.length
, 而是 i <= elements.length
。所以最后一个元素实际上超出了流。方便的是,这将自动成为 Optional.empty,你可以在每一个测试的结尾中看到。
同 map()
一样 , Optional.map()
执行一个函数。它仅在 Optional 不为空时才执行这个映射函数。并将 Optional 的内容提取出来,传递给映射函数。代码示例:
import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.Stream;
class OptionalMap {
static String[] elements = {"12", "", "23", "45"};
static Stream<String> testStream() {
return Arrays.stream(elements);
}
static void test(String descr, Function<String, String> func) {
System.out.println(" ---( " + descr + " )---");
for (int i = 0; i <= elements.length; i++) {
System.out.println(
testStream()
.skip(i)
.findFirst() // Produces an Optional
.map(func));
}
}
public static void main(String[] args) {
// If Optional is not empty, map() first extracts
// the contents which it then passes
// to the function:
test("Add brackets", s -> "[" + s + "]");
test("Increment", s -> {
try {
return Integer.parseInt(s) + 1 + "";
} catch (NumberFormatException e) {
return s;
}
});
test("Replace", s -> s.replace("2", "9"));
test("Take last digit", s -> s.length() > 0 ?
s.charAt(s.length() - 1) + "" : s);
}
// After the function is finished, map() wraps the
// result in an Optional before returning it:
}
输出结果:
映射函数的返回结果会自动包装成为 Optional。Optional.empty 会被直接跳过。
Optional 的 flatMap()
应用于已生成 Optional 的映射函数,所以 flatMap()
不会像 map()
那样将结果封装在 Optional 中。代码示例:
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
class OptionalFlatMap {
static String[] elements = {"12", "", "23", "45"};
static Stream<String> testStream() {
return Arrays.stream(elements);
}
static void test(String descr,
Function<String, Optional<String>> func) {
System.out.println(" ---( " + descr + " )---");
for (int i = 0; i <= elements.length; i++) {
System.out.println(
testStream()
.skip(i)
.findFirst()
.flatMap(func));
}
}
public static void main(String[] args) {
test("Add brackets",
s -> Optional.of("[" + s + "]"));
test("Increment", s -> {
try {
return Optional.of(
Integer.parseInt(s) + 1 + "");
} catch (NumberFormatException e) {
return Optional.of(s);
}
});
test("Replace",
s -> Optional.of(s.replace("2", "9")));
test("Take last digit",
s -> Optional.of(s.length() > 0 ?
s.charAt(s.length() - 1) + ""
: s));
}
}
输出结果:
同 map()
,flatMap()
将提取非空 Optional 的内容并将其应用在映射函数。唯一的区别就是 flatMap()
不会把结果包装在 Optional 中,因为映射函数已经被包装过了。在如上示例中,我们已经在每一个映射函数中显式地完成了包装,但是很显然 Optional.flatMap()
是为那些自己已经生成 Optional 的函数而设计的。
假设你的生成器可能产生 null
值,那么当用它来创建流时,你会自然地想到用 Optional 来包装元素。如下是它的样子,代码示例:
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class Signal {
private final String msg;
public Signal(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
@Override
public String toString() {
return "Signal(" + msg + ")";
}
static Random rand = new Random(47);
public static Signal morse() {
switch (rand.nextInt(4)) {
case 1:
return new Signal("dot");
case 2:
return new Signal("dash");
default:
return null;
}
}
public static Stream<Optional<Signal>> stream() {
return Stream.generate(Signal::morse)
.map(signal -> Optional.ofNullable(signal));
}
}
当我们使用这个流的时候,必须要弄清楚如何解包 Optional。代码示例:
import java.util.*;
public class StreamOfOptionals {
public static void main(String[] args) {
Signal.stream()
.limit(10)
.forEach(System.out::println);
System.out.println(" ---");
Signal.stream()
.limit(10)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(System.out::println);
}
}
输出结果:
在这里,我们使用 filter()
来保留那些非空 Optional,然后在 map()
中使用 get()
获取元素。由于每种情况都需要定义“空值”的含义,所以通常我们要为每个应用程序采用不同的方法。