Java基礎系列-Optional

唯一浩哥發表於2020-09-27

原創文章,轉載請標註出處:https://www.cnblogs.com/V1haoge/p/10755368.html

一、概述

Optional的引入是為了解決null的問題,那麼到底是解決null的什麼問題呢?

我們知道當我們針對null呼叫方法的之後,就會丟擲空指標異常,Optional就是為了解決這個問題而來的。

Optional通過封裝目標物件的方式來表示,當我們使用的時候,Optional是必然存在的,因為如果結果為null,會返回一個固定的EMPTY例項,這樣就不會存在null引用的問題了。

那該如何來使用Optional呢?

我們不能濫用Optional,只有在邏輯上(業務上)可能為null時才使用Optional來封裝目標物件,如果邏輯上(業務上)肯定不可能為null時,就不要使用Optional封裝物件,這樣當閱讀程式碼的人看到有Optional則表示目標物件是可以為null的。

這樣之後,如果程式中又出現了null指標異常,那麼只能是你的程式碼邏輯有誤,而不可能是非邏輯原因了,也就是不該為null的時候出現了null,這肯定是你邏輯搞錯了。

二、Optional

2.1 建立Optional例項

2.1.1 建立一個空的Optional

public class OptionalTest {
    public static void main(String[] args) {
        Optional<String> o = Optional.empty();// 建立一個空Optional
    }
}

2.1.2 建立一個有值的Optional

public class OptionalTest {
    public static void main(String[] args) {
        Optional<String> op = Optional.of("123");// 建立一個目標物件必須有值的Optional
    }
}

這種建立方式,如果of的引數為null,則直接丟擲異常。

2.1.3 建立一個可為空的Optional

public class OptionalTest {
    public static void main(String[] args) {
        Optional<String> opt = Optional.ofNullable(null);// 建立一個目標物件可為null的Optional
    }
}

2.2 中間操作-處理Optional

2.2.1 filter

這個filter是一個校驗器,由於Optional中也只有一個元素,稱其為過濾有點太大了,如果Optional中的元素滿足給定的校驗條件,則將封裝元素的Optional返回,否則返回空的Optional。

public class OptionalTest {
    public static void main(String[] args) {
        filterTest();
    }
    public static void filterTest(){
        Optional<String> os = Optional.of("123456").filter(e -> e.length()>7);
        System.out.println(os.isPresent());
    }
}

執行結果:

false

因為字串“123456”長度小於7,所以返回一個空的Optional。

2.2.2 map

map是對映之意,就是針對Optional封裝的元素進行操作,然後返回一個封裝著操作結果的新的Optional。

public class OptionalTest {
    public static void main(String[] args) {
        mapTest();
    }
    public static void mapTest(){
        Optional<Integer> oi = Optional.of("abcdefg").map(e -> e.length());
        System.out.println(oi.get());
    }
}

執行結果:

7

2.2.3 flatMap

這個方法是扁平化對映,所謂扁平就是處理巢狀的Optional。

比如有一個Person類,其中有一個Optional型別的欄位name,我們在別處獲取到了一個Optional型別的例項,現在想要獲取到這個Person的的姓名,這就是巢狀的Optional。

class Person{
    Optional<String> name;
    public Person(String name){
        this.name = Optional.of(name);
    }

    public Optional<String> getName(){
        return name;
    }
}

如果我們使用map獲取到的是如下的:

public class OptionalTest {
    public static void main(String[] args) {
        flatMapTest();
    }
    public static void flatMapTest(){
        Optional<Person> op = Optional.of(new Person("huahua"));
        Optional<Optional<String>> oos = op.map(Person::getName);
        String name = oos.get().orElseGet(()->"noName");
        System.out.println(name);
    }
}

而我們使用flatMap的話就可以是這樣的:

public class OptionalTest {
    public static void main(String[] args) {
        flatMapTest();
    }
    public static void flatMapTest(){
        Optional<Person> op = Optional.of(new Person("huahua"));
        Optional<String> os = op.flatMap(Person::getName);
        String name = os.orElseGet(()->"noName");
        System.out.println(name);
    }
}

巢狀的Optional要獲取到目標物件,必須通過多次去殼才行,這也是使用map對映的原理,但是使用flatMap,扁平化功能,一次去殼操作就完成了。

2.3 從Optional中獲取目標值

有四種方式:

  • public T get():直接獲取Optional中的值,如果是空的要丟擲NoSuchElementException異常
  • public T orElse(T other):如果Optional不為空,則將值返回,否則返回指定的other(預設值)
  • public T orElseGet(Supplier<? extends T> other):如果Optional不為空,則返回值,否則將通過指定方式other生成的值返回
  • public T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X:如果Optional不為空,返回值,否則丟擲由指定的exceptionSupplier生成的異常
public class OptionalTest {
    public static void main(String[] args) {
        getTest();
        orElseTest();
    }
    public static void getTest(){
        Optional<String> os = Optional.of("123456");
        System.out.println(os.get());
    }
    public static void orElseTest(){
        Optional<String> os = Optional.empty();
        System.out.println(os.orElse("default"));
        System.out.println(os.orElseGet(()->"default"));
        System.out.println(os.orElseThrow(RuntimeException::new));
    }
}

執行結果:

123456
default
default
Exception in thread "main" java.lang.RuntimeException
	at java.util.Optional.orElseThrow(Optional.java:290)
	at com.dh.stream.OptionalTest.orElseTest(OptionalTest.java:29)
	at com.dh.stream.OptionalTest.main(OptionalTest.java:17)

三、使用方式

Optional使用時不能直接get,這時會走進老路,get的時候可能為null,就會丟擲異常,這時候你就會想要在get之前進行isPresent判斷,這個不使用Optional又有什麼區別的。

我們使用Optional就是為了簡化null判斷,所以拒絕使用get方法,Optional提供了正確使用的方法是:orElse、orElseGet、orElseThrow三個方法。

使用orElse方法,我們可以從非空的Optional中獲取到值,如果是空的Optional,可以返回orElse方法引數指定的預設值。

使用orElseGet方法,我們可以在空的Optional的情況下主動構建一個預設返回結果。

orElseThrow方法,在空Optional的情況下會丟擲一個指定的異常。

四、總結

說了這麼多,最終是為了在開發中使用Optional,正如開始時說的,我們要明確Optional規避的是那種null,不能在所有的地方都使用它。

當專案的業務規則下某個物件可能為null(就是業務允許的null),這種情況下,出現null是正常現象,這種情況需要規避,我們使用Optional封裝目標物件,保證不會存在null呼叫丟擲空指標。

但是如果在業務規則下某個物件不可能為null(就是業務不允許為null),這種情況下,出現null就是程式出錯了,並不是正常現象,這種時候我們不能用Optional去封裝目標物件來規避問題,而是要直接使用,一旦出錯就可以及時的排查問題,不至於被Optional將問題給隱藏掉。

相關文章