0%

stream and lambda(11) - Optional 简介

提到著名的 NullPointerException,相信大家都熟悉。空指针异常不单单经常出现在我们的测试环境的代码里,在生产环境其实也不少见。而为了尽可能的避免空指针异常的出现,我们更是小心翼翼地在代码加各种判断。

Java8 则引入了一个新的类 Optional,可以有效的避免我们一不小心就写了 NPE 的代码。

先看源码

源码节选,有省略,基于 Java8。(Java9 之后有增强)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public final class Optional<T> {

//预设一个实例化对象,无值
private static final Optional<?> EMPTY = new Optional<>();

//Optional 存储的值
private final T value;

//默认构造器,不设值
private Optional() {this.value = null;}

//返回一个无值的 Optional 对象
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}

private Optional(T value) {
this.value = Objects.requireNonNull(value);
}

//传入的值不能为空
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}

//传入的值可以为空
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}

//有值才可以 get
public T get() {
if (value == null) { throw new NoSuchElementException("No value present"); }
return value;
}

// 判断是否有值
public boolean isPresent() {
return value != null;
}

//如果有值执行逻辑
public void ifPresent(Consumer<? super T> consumer) {
if (value != null) consumer.accept(value);
}

//满足条件没事,不满足,返回 empty 的 Optional
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent()) return this;
else return predicate.test(value) ? this : empty();
}

//无值返回 empty,有值执行逻辑
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) return empty();
else { return Optional.ofNullable(mapper.apply(value));}
}

//无值返回 empty,有值执行逻辑
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) return empty();
else { return Objects.requireNonNull(mapper.apply(value)); }
}

//没 value 就回返 T
public T orElse(T other) {
return value != null ? value : other;
}

//没 value 就通过传入的Supplier获取
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}

//没 value 就抛指定异常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
}

Optional 源码其实很简单,没有复杂的实现。

Optional 应该怎样用?

创建 Optional

创建有值的 Optional,可以用以下两种方法:

public static <T> Optional<T> of(T value)
public static <T> Optional<T> ofNullable(T value)

of 方法要求传入的 value != null,所以用的时候要注意,否则还是会产生 NPE

ofNullable 传入的值可以为 null,所以当你不确定值到底会不会在某种情况下为 null 的时候,可以使用该方法。

取值

取值的话,我们可以用 get 方法。但问题是,虽然不会导致 NPE,但是又引入了 NoSuchElementException。所以如果使用 get 方法也是要通过 isPresent 先判断的。

经过 isPresent 判断的话,还是相当于我们在代码里要主动地加判断。有没有办法可以让我们不加判断呢?有。

public T orElse(T other)
public T orElseGet(Supplier<? extends T> other)

这两个方法可以设置一个默认值,如果 value == null,取出来的就是默认值。

需要注意的是,orElse 传入的是一个对象,也就意味着,如果传的不是 null,那么它就已经经过实例化了,虽然不一定能用到。而 orElseGet 传入的则是一个 Supplier,而 supplier.get 不一定会执行。比如:

1
2
3
4
5
6
7
8
9
public void getTest() {
Optional<String> emptyOpt = Optional.empty();
String emptyDefault = emptyOpt.orElse("emptyStr");

Optional<String> demoOpt = Optional.of("demo");
String demoStr = demoOpt.orElseGet(() -> "demoStr");
System.out.println(emptyDefault);
System.out.println(demoStr);
}

在上面的例子中,emptyStr 一定会被创建,因此传入的就是创建好的。而 Supplier.get() 并不会被立即执行,因此传入的是一个 Supplier,只有 !demoOpt.isPresent 才会执行 get 方法。

当然,Optional 提供的还有 mapfilterflatmap,这几个 API 在 Stream 里也有,可以触类旁通。

空值自定义异常

如果对于空值,我们真的想抛一个自定义异常,其实也是支持的。

T orElseThrow(Supplier<? extends X> exceptionSupplier)

1
2
3
4
5
6
7
8
public void exceptionTest() {
Optional<String> emptyOpt = Optional.empty();
System.out.println(111);
String value = emptyOpt.orElseThrow(
() -> new RuntimeException("xxx must have a value")
);
System.out.println(value);
}

orElseThrow 给了我们更多自由,让我们不再局限于 NPE,可以根据业务实际情况来抛异常。

Stream 中用到的 Optional

Stream 中也有不少终止操作用到 Optional。

Optional<T> reduce(BinaryOperator<T> accumulator);
Optional<T> min(Comparator<? super T> comparator);
Optional<T> max(Comparator<? super T> comparator);
Optional<T> findFirst();
Optional<T> findAny();

看,Stream 和 Optional,真的很配呢。

总结

  • Optional 可以有效减少代码可能出现的 NullPointerExceptions,但是不能完全避免,同时还有可能导致 NoSuchElementException。但是总的来说,它真的可以帮我们减少程序错误,可读性也更高。
  • Optional 可以配合 Stream 一起使用。
  • 谨慎使用 get 方法,它可能会抛异常。
  • orElse 是直接设置默认值,orElseGet 是提供一个默认值产生的方法,但是不一定会用上。
  • 本质上来讲,Optional 相当于一个容器,里面可以放东西,但不一定放了东西。

注:本文配套代码可在 github 查看:stream-and-lambda