stream and lambda(11) - Optional 簡介

語言: CN / TW / HK

提到著名的 NullPointerException ,相信大家都熟悉。空指標異常不單單經常出現在我們的測試環境的程式碼裡,在生產環境其實也不少見。而為了儘可能的避免空指標異常的出現,我們更是小心翼翼地在程式碼加各種判斷。

Java8 則引入了一個新的類 Optional ,可以有效的避免我們一不小心就寫了 NPE 的程式碼。

先看原始碼

原始碼節選,有省略,基於 Java8。(Java9 之後有增強)

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 不一定會執行。比如:

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)

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,真的很配呢。

總結

get

注:本文配套程式碼可在 github 檢視: stream-and-lambda