# Optional 详解

# 简介

在 Java 8 之前,对于方法的返回值,没有一个很好的方式去表示返回的值是否为 null,因此很多时候需要手动判断,甚至因为没有判断而发生 NPE 异常。于是 Java 8 专门新出了一个类 Optional,这个类的解释是:它是一个可以包含空或者非空值的容器,如果容器内的对象不为空,则 ifPresent 返回 true,否则返回 false。除此之外,Optional 还提供了其他的 API,方便对容器的对象进行获取、判断、转换、过滤等。

值得一提的是,Optional 类是用来表达方法返回结果可能为空的可能性

# 基本使用

Optional 的代码比较简单,注释加代码只有几百行。

Optional 是一个 final 类型的类,没有实现 Serializable 接口,它只有一个私有的构造函数。

/**
 * 用来表示值为 null 的实例
 */
private static final Optional<?> EMPTY = new Optional<>();

/**
 * Optional 包含的值
 */
private final T value;

/**
 * 私有构造函数
 */
private Optional() {
    this.value = null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 创建

Optional 有三种方法可以获得实例化对象。

/**
 * 可以返回一个内置的,表示空的 Optional 实例
 */
public static <T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

/**
 * 根据传入的 value 创建一个 Optional 实例
 * 如果 value 为 null 则会抛出 NPE
 */
public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

/**
 * 根据传入的 value 创建一个 Optional 实例
 * 如果 value 为 null 则返回一个表示空的实例
 */
public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 判断

Optional 提供了 API 用于判断值是否存在,或者在值存在的情况下执行一些操作。

/**
 * 如果 value 不为 null,则返回 true,否则返回 false
 */
public boolean isPresent() {
    return value != null;
}

/**
 * 如果值存在,则会调用给定的 action 动作
 */
public void ifPresent(Consumer<? super T> action) {
    if (value != null) {
        action.accept(value);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 获取

Optional 提供了 API 用于获取内部的值,或者在值不存在的情况下,返回另一个默认值或抛出异常。

/**
 * 如果值存在,则返回值,否则会判处 NoSuchElementException 异常。
 * 因此对于 Optional 的返回值,一定要先判断值是否存在,类似于编译时
 * 检查异常,一定要你去处理
 */
public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

/**
 * 返回内部的值,否则返回一个默认值
 */
public T orElse(T other) {
    return value != null ? value : other;
}

/**
 * 返回内部值,如果内部值为空则执行 Supplier 实例的 get 方法返回默认值
 * 只有在 value == null 时,才会执行 Supplier 实例的 get 方法
 */
public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}

/**
 * 返回内部值,如果内部值为空则抛出 Supplier 实例返回的异常
 * <X extends Throwable> 表示 Supplier 实例返回的是 Throwable 的子类
 */
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}
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

# 转换

Optional 也提供类似 Stream 的 API,通过 map 或者 flatMap 方法,将值转换为另一种类型。

/**
 * 如果 value 存在,则使用传入的 mapper 对象进行类型转换,
 * 并且会将转换后的对象包装成 Optional 实例。
 * 
 * 如果 value 不存在,则会返回一个空的 Optional 实例。
 */
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));
    }
}

/**
 * 作用同 map 方法,区别点是 flatMap 传入的 mapper 对象本身
 * 就会返回一个 Optional 实例。
 */
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));
    }
}
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

# 过滤

filter 是用来对 Optional 内的值进行校验,类似于 if(checkValue(returnValue)) 这样的做法。

/**
 * 传入一个断言对象 predicate,如果 value 符合 predicate 的规则
 * 的话,则将当前 Optional 对象返回,否则返回一个空的 Optional 实例
 */
public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}
1
2
3
4
5
6
7
8
9
10
11

# 最佳实践

# 不要滥用 Optional

需要对某个传入参数或者其他对象进行判空时,直接通过 if 来判断效率更高,通过 Optional.ofNullable 不仅降低可读性还产生一个无谓的 Optional 实例。

String str = Optional.ofNullable(param).orElse("default");
1

# 在 Java Bean 中慎用 Optional

不要使用 Optional 作为 Bean 的成员变量,如果想表达某个字段可能不存在,可以在 getter 的返回值上做处理。因为 Optional 没有实现 Serializable 接口,不可序列化。

public class User {
    private String phoneNumber;
  
  	public Optional<String> getPhoneNumber() {
        return this.phoneNumber;
    }
}
1
2
3
4
5
6
7

# 避免将 Optional 作为方法的参数类型

当想表达对于一个方法来说,其参数值是可以为空的时候,不建议使用 Optional 作为参数类型,它不仅需要将参数多做一层包装,而且会让方法参数列表变得长,而且不利于阅读。

public void fun(Optional<String> p1, Optional<Integer> p2) {
  
}
1
2
3

可以考虑将方法拆成多个重载方法。

public void fun(String p1, Integer p2);
public void fun(String p1);
public void fun(Integer p2);
public void fun();
1
2
3
4

# 不要在使用 Optional 去包装集合

类似于 List、Map、Set 这些集合类,Collections 已经有对应的空值对象了。

Collections.emptyList();
Collections.emptySet();
Collections.emptyMap();
Stream.empty()
1
2
3
4

# 🌟不要给 Optional 对象赋值 null

当表达 Optional 为空时,请用 empty() 方法。

public Optional<User> getUserInfo() {
  	Optional<User> result = Optional.empty();
    ...
    return result;
}
1
2
3
4
5

# 🌟确保值存在再调用 Optional 的 get 方法

从源码可以看出来,如果 value 为 null,调用 get 方法是会抛出异常的。而且 IDEA 也会识别并给出警告。

Optional 强迫开发者去关注返回值为空的情况,并且需要去检查返回值。

# 🌟熟悉 Optional 的 API

熟悉 Optional 的 API ,并在使用 Optional 的时候避免使用 if-else 这种方式,而是通过 Optional.orElse() Optional.orElseGet() Optional.ifPresent() 可以避免手写 if-else 语句,使代码更简洁。

# 使用 equals 而不是 == 比较 Optional

Optional 的 equals 方法已经实现了内部值比较。

@Override
public boolean equals(Object obj) {
  	if (this == obj) {
    		return true;
  	}

  	if (!(obj instanceof Optional)) {
    		return false;
  	}

  	Optional<?> other = (Optional<?>) obj;
  	return Objects.equals(value, other.value);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 参考文章

  • https://zhuanlan.zhihu.com/p/128481434
上次更新: 2023/12/10