Java8 新特性之 Optional 容器对象学习

205 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

1. Optional 源码介绍

1.1 类的定义

Optional, 是 Java8 之后在 java.util 包中新增的容器对象类型,可以创建得到一个容器对象,容器中可能包含也可能不包含一个非空的对象值。

public final class Optional<T> {
    ...
}

1.2 构造函数

Optional 类中提供了两种构造方法,即无参构造和一个有参构造方法,且构造方法都是私有化定义的,不能够在类的外部使用 new 关键字构造对象示例。

// 无参构造,创建包含 null 值的 Optional 容器对象
private Optional() {
    this.value = null;
}

// 有参构造方法,参数类型为泛型类,根据指定的对象创建合适的 Optional 容器对象,并将对象作为 value 存在
// 构造方法中会保证传入的对象不为 null
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

1.3 类变量

Optional 容器是允许创建一个空的容器对象实例的,因此类中提供了一个静态变量 EMPTY,另外还定义了泛型类型 value 来承载创建容器时传入的对象。

// 空的 Optional 容器对象,内部调用无参构造方法创建
private static final Optional<?> EMPTY = new Optional<>();

// 容器中的对象值,使用有参构造方法初始化时 value 即是传入对象,使用无参构造时 value = null
private final T value;

1.4 类的静态方法

尽管 Optional 类的构造方法全部私有化,不允许外部直接创建,但是 Optional 中还是提供了可调用的静态方法以便外部创建 Optional 的对象。

// Optional 空容器对象也是私有的,提供了 empty() 方法来获取 EMPTY
public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

// of() 方法根据传入对象创建 Optional 容器对象并返回,如果对象为 null,在使用有参构造创建时会抛出空指针异常
public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

// 与 of() 方法不同的是,ofNullable() 方法内会判断如果参数对象为 null,会返回一个空的容器对象
public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

1.5 容器中 value 对象简单操作方法

  • Optional 容器对象中的 value 对象是可能为 null 的,可以使用 isPresent() 方法来判断是否为 null;

  • 还可以通过对象提供的 get() 方法直接获取 value 的值,如果为 null 则会抛出异常信息;

  • ifPresent() 方法则接收一个 Consumer 消费对象,如果 Optional 容器中的 value 不为 null,则会对 value 对象进行消费。

// 获取容器对象中的 value
public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

// 获取容器对象中的 value 是否存在,即不为 null 时返回 true
public boolean isPresent() {
    return value != null;
}


// 如果 value 存在,则会执行传入的操作对象,消费 value
public void ifPresent(Consumer<? super T> consumer) {
    if (value != null){
        consumer.accept(value);
    }
}

1.6 容器中 value 对象进阶操作方法

类似 Stream 流,Optional 也提供了对容器中对象进行筛选和映射的操作,即 filter() 方法和 map() 方法。

// filter() 方法传入一个条件断言对象 Predicate,当 value 不为 null 时进行断言判断,满足则返回 Optional 对象本身,不满足则返回空的 Optional
public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent()){
        return this;
    } else{
        return predicate.test(value) ? this : empty();
    }
}

// 如果 value 不存在则返回空 Optional 独享,否则使用传入的 Function 对象操作 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));
    }
}

// flatMap() 方法类似 map(),不同的是该方法接收的函数中指定了映射结果为 Optional 类型,因此最终结果不需要再次封装,只需要判断非 null 即可
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.7 结果方法

Optional 类作为对象容器,对需要操作的对象进行包装并进行相关操作,但是最终还是要得到容器中的对象内容, Optional 容器对象中提供了 get() 方法来获取容器中内容,但是当 value 为 null 时会抛出异常。

为了使用更加方便,Optional 容器对象还提供了 orElse() 等方法支持在操作时传入默认内容,如果不满足过程条件,则返回默认值。

// 类似 get() 获取 value 对象,接收默认参数,value 为 null 时不报错而是返回默认内容
public T orElse(T other) {
    return value != null ? value : other;
}

// 传入 Supplier 对象,如果 value 为 null,则返回 Supplier 对象的 get() 信息
public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}


// 传入 Supplier 类型的异常提供对象,如果 value 为 null,则返回对应的异常信息
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

1.8 其他重写方法

Optional 作为对象类型,其本身会继承 Object 基类,并且重写了其中的 equals()、hashCode()、toString() 等方法。

// Optional 的 equals() 方法最终比较的是其中的 value
@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);
}



// hashCode() 方法也重写了,使用其中 value 对象的 hashCode()
@Override
public int hashCode() {
    return Objects.hashCode(value);
}

// 根据 value 是否为 null 输出不同内容
@Override
public String toString() {
    return value != null ? String.format("Optional[%s]", value) : "Optional.empty";
}

2. Optional 使用

实际代码中使用 Optional 容器对象能够减少代码中对对象为 null 的判断,可以有效减少程序中空指针异常的出现。

2.1 空指针判断逻辑替换

对于深度封装的对象是否为 null 的判断,如果使用 if 可能要多次判断或多层级判断,可以使用 Optional 对象结合 map() 等方法实现。

// 多层 if 判断
Address address = null;
if(Objects.nonNull(user)){
    address = user.getArrdess();
}
String city = null;
if(Objects.nonNull(address)){
    city = address.getArrdess();
}
return city;

// 三目表达式多级判断方式
Address address = Objects.nonNull(user) ? address = user.getArrdess() :null;
return Objects.nonNull(address) ? address.getCity() : null;

// Optional 方式
String city = Optional.ofNullable(user)
    .map(u-> u.getAddress())
    .map(a->a.getCity())
    .orElseThrow(()->new Exception("指定空指针异常"));
  • 如果对象属性的 get() 方法返回类型是一个 Optional 容器对象而不是普通对象,此时应该使用 flatMap() 来代替 map()。

2.2 if ... else ... 逻辑替换

对于普通的 if ... else ... 逻辑也可以使用 Optional 容器对象方法实现。

// if...else...逻辑
User userTemp;
if(Objects.nonNull(user) && "Tom".equals(user.getName)){
    userTemp = user;
}else{
    User userTemp = new User();
    userTemp.setName("Tom");
}

// Optional 方法
User userTemp = Optional.ofNullable(user)
    .filter(u -> "Tom".equals(u.getName()))
    .orElseGet(()-> {
        User userTemp = new User();
        userTemp.setName("Tom");
        return userTemp;
    });
  • 在程序中使用 Optional 对象是需要注意,并不一定所有的 if 逻辑都需要使用 Optional 代替,只在合适的时候使用 Optional 来减少空指针异常的发生,而不要过度包装使用。