stream and lambda(11) - Optional 簡介
提到著名的 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
提供的還有 map
、 filter
、 flatmap
,這幾個 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
- stream and lambda(18) - 終止操作之 stream 收集器 collectors
- stream and lambda(15) - 終止操作之 stream 陣列操作 toArray
- stream and lambda(13) - 終止操作之計數 count 與比較 min、max
- stream and lambda(12) - 終止操作之查詢與匹配(findAny、findFirst、allMatch、anyMatch、noneMatch)
- stream and lambda(11) - Optional 簡介
- stream and lambda(10) - 中間操作之排序sorted與除錯peek
- stream and lambda(9) - 中間操作之map操作(map、flatmap)
- stream and lambda(8) - 中間操作之篩選操作(filter、distinct、limit、skip)
- stream and lambda(7) - stream 的建立
- stream and lambda(6) - stream 簡介
- stream and lambda(5) - lambda 表示式最佳實踐
- stream and lambda(3) - jdk 提供的函式式介面
- stream and lambda(2) - 函式式介面簡介
- stream and lambda(1) - lambda 簡介