天天看點

【Java8特性】之Optional詳解

【Java8特性】彙總

文章目錄

    • Optional類介紹
    • Optional類的使用
      • 建立Optional執行個體
        • Optional.empty()
        • Optional.of()
        • Optional.ofNullable()
      • 通路 Optional 對象的值
        • get()
        • isPresent()
        • ifPresent()
        • orElse(T other)
        • orElseGet(Supplier<? extends T> other)
        • orElse() 和 orElseGet() 的不同之處
        • orElseThrow(Supplier<? extends X> exceptionSupplier)
      • 過濾值
        • filter(Predicate<? super T> predicate)
      • 轉換值
        • map()
        • flatMap()
    • Optional 類的最佳實踐

Optional類介紹

Java 8

引入的一個很有趣的特性是 Optional 類。Optional 類主要解決的問題是臭名昭著的空指針異常(

NullPointerException

) —— 每個 Java 程式員都非常了解的異常。

本質上,這是一個包含有可選值的包裝類,這意味着 Optional 類既可以含有對象也可以為空。

Optional 是 Java 實作函數式程式設計的強勁一步,并且幫助在範式中實作。但是 Optional 的意義顯然不止于此。

我們從一個簡單的用例開始。在 Java 8 之前,任何通路對象方法或屬性的調用都可能導緻

NullPointerException

在這個小示例中,如果我們需要確定不觸發異常,就得在通路每一個值之前對其進行明确地檢查:

// 查詢國家碼
    public void getIsoCode(User user) {
        if (user != null) {
            Address address = user.getAddress();
            if (address != null) {
                Country country = address.getCountry();
                if (country != null) {
                    String isocode = country.getIsocode();
                    if (isocode != null) {
                        isocode = isocode.toUpperCase();
                    }
                }
            }
        }
    }
           

你看到了,這很容易就變得冗長,難以維護。

為了簡化這個過程,我們來看看用 Optional 類是怎麼做的。從建立和驗證執行個體,到使用其不同的方法,并與其它傳回相同類型的方法相結合,下面是見證 Optional 奇迹的時刻。

public String getIsoCode(User user) {
        return Optional.ofNullable(user)
                .map(a -> a.getAddress())
                .map(a -> a.getCountry())
                .map(a -> a.getIsocode())
                .map(a -> a.toUpperCase())
                .orElse("CHINA");
    }
           

Optional類的使用

Optional類有很多方法,通過

of()

ofNullable()

構造Optional執行個體,Optional類擁有

value屬性

,通過

get()

擷取執行個體的value屬性值,通過組合這些方法,最終實作上述解決NullPointerException問題的。下面我們來認識一些基礎的方法,逐漸揭秘。

建立Optional執行個體

Optional.empty()

empty()方法傳回一個空的Optional對象,其

value屬性

null

,用法如下。

empty()方法之是以放在第一個介紹是有原因的,因為涉及到

Optional的構造是私有的

,這個知識很重要,我們來看下源碼:

/*
 * @since 1.8   标記1.8版本後才有的
 */
public final class Optional<T> {
  //内部執行個體化一個Optional執行個體,命名為EMPTY ,全局唯一的
  private static final Optional<?> EMPTY = new Optional<>();
  //value屬性
  private final T value;
  //empty()方法實際上 就是傳回之前那個命名為EMPTY 執行個體
  public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
  }
  //構造函數,注意是私有的,無法在外部用new通路
  private Optional() {
      this.value = null;
  }
           

Optional構造方法是私有的,也就是說不允許外部通過new的形式建立對象

。構造方法是供Optional類内部使用。Optional内部維護這個一個value的變量,無參數建構的時候value為null

Optional.empty()傳回的執行個體對象時全局唯一的,多次調用,結果是同一個對象:

@org.junit.Test
  public void testEmpty() {
      Assert.assertEquals(Optional.empty(), Optional.empty());   //我們調用了2次empty(),并比較equals
     Assert.assertTrue(Optional.empty() == Optional.empty());  //比較對象位址
  }
           

Optional.of()

建立value屬性為指定值的 Optional執行個體,就是用一個Optional對象,包裹了給定的值。用法如下:

通過of方法所構造出的Optional對象有如下特點:

  • 當指定值為空時,會報NullPointerException異常
  • 當指定值不為空時,正常構造Optional對象

我們通過源碼來分析下原因:

public final class Optional<T> {    //Optional類
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);     //調用私有的構造函數
    }
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);   //調用Objects.requireNonNull限制指定值非空
    }
 }
 
public final class Objects {   //Objects工具類
     public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();  //如果為空,則抛出異常
        return obj;
    }   
           
注意: 隻有你确定對象100%不為 null 的時候才能使用 of(),否則會報NullPointerException

Optional.ofNullable()

建立value屬性為指定值的 Optional執行個體,如果指定為空,則傳回一個空的Optional執行個體。

ofNullable用法和of()相似,但是差別在于ofNullable對指定值無限制,即使傳入一個null值,也不會報錯,通過源碼來分析原因:

public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value); //給定值為空,傳回空的Optional執行個體,不為空,則複用of()構造一個新的執行個體
    }
           
如果對象即可能是 null 也可能是非 null,你就應該使用 ofNullable() 方法,避免用of()方法

通路 Optional 對象的值

我們在前面知道Optional就是包裹了給定的值,進行了封裝,自身有個

value屬性

,那麼擷取對象的值,就是擷取

value屬性

get()

從 Optional 執行個體中取回

value屬性

@Test
public void whenCreateOfNullableOptional_thenOk() {
    String name = "John";
    Optional<String> opt = Optional.ofNullable(name); //建構一個Optional 執行個體
 
    assertEquals("John", opt.get());   //擷取Optional 執行個體的值
}
           

不過,需要注意的是,當value屬性為空時會報錯:

@Test(expected = NoSuchElementException.class)
    public void testGetThrowException() {
        Optional.empty().get();
    }
           

通過源碼分析下原因:

public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");  //判斷value屬性為空時抛出異常
        }
        return value;  //傳回value屬性
    }
           
為了避免出錯,可以在調用

get()

之前先驗證是否有值,

isPresent()

應運而生

isPresent()

判斷

value屬性

值是否存在。

value屬性

== null 表示不存在,非null表示存在。

例子:

@org.junit.Test
    public void testIsPresent() {
        Optional<Integer> optional1 = Optional.ofNullable(1);
        Optional<Integer> optional2 = Optional.ofNullable(null);
      
        Assert.assertEquals(optional1.isPresent(), true);   //存在
        Assert.assertEquals(optional2.isPresent(), false);  //不存在
    }
           
注意:方法的

字首是is,不是if

,避免和下面的ifPresent()混淆

由于null、空字元串比較容易混淆 “存在”的含義,我們上源碼:

public boolean isPresent() {
        return value != null;     //與null進行比較
    }
           
由源碼得出結論:空字元串表示存在

ifPresent()

方法定義:

ifPresent()不是擷取值,該方法沒有傳回值。之是以放在此處介紹,是因為名稱和前面的isPresent()容易混淆。

如果option對象的value屬性值不是null,則調用consumer對象,并将value屬性值作為入參傳入,否則不調用。Consumer可以了解為一個Lambda 表達式(Lambda 章節專門講解Consumer用法),并且限定隻有一個入參:

Optional<User> opt = Optional.ofNullable(user);
opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));
           

這個例子中,隻有 user 使用者不為 null 的時候才會執行斷言。

orElse(T other)

結構為:

orElse(T other)是一個工具方法,作用類似

get()

,可以擷取

value屬性

的值,之是以說是一個工具方法,是因為當value屬性值為空時,傳回的值是指定的other參數值:

@org.junit.Test
    public void testOrElse() {
        Optional<String> optional1 = Optional.ofNullable("zhangsan");   //option執行個體非空
        Optional<String> optional2 = Optional.ofNullable(null);  //option執行個體為空

        Assert.assertEquals(optional1.orElse("lisi"), "zhangsan");     //option執行個體非空,傳回value屬性值
        Assert.assertEquals(optional2.orElse("lisi"), "lisi"); //option執行個體為空,則傳回指定的新值
    }
           

源碼也很簡單:

public T orElse(T other) {
        return value != null ? value : other; //value不為空,則傳回value;為空則傳回other
    }
           

這個方法很重要,比如我們經常在處理傳回值時,如果值是一個null,我們希望轉化為空字元串,這樣傳回值在外部被調用時就不會報空指針了

public String getValue(){      //正常方式
        String result = doSomething();
        if(result==null){
            return "";   //把null轉化為空字元串
        }
    }
    
    public String getValue(){  //Optional方式
        String result = doSomething();
        return   Optional.of(result).orElse("");   //自動轉化為空字元串
    }
           

orElseGet(Supplier<? extends T> other)

用法為:

從字面上就能看出作用與

orElse()

類似,也是擷取

value屬性

值,但行為略有不同。這個方法會在value屬有值的時候傳回value屬值,如果沒有值,它會執行作為參數傳入的

Supplier(供應者) 函數式接口

,并将傳回其執行結果:

@org.junit.Test
    public void testOrElseGet() {

        Optional<Integer> optional1 = Optional.ofNullable(1); //option執行個體非空,并且value屬性值為1
        Optional<Integer> optional2 = Optional.ofNullable(null);  //option執行個體為空

        Assert.assertEquals(optional1.orElseGet(() -> {
            return Integer.valueOf(1000);        //表達式,執行結果為1000
        }), Integer.valueOf(1));   //option執行個體非空,傳回value屬性值1

        Assert.assertEquals(optional2.orElseGet(() -> {
            return Integer.valueOf(1000);  //表達式,執行結果為1000
        }), Integer.valueOf(1000));  //option執行個體為空,則傳回Supplier Lambda結果,1000

    }
           

orElse() 和 orElseGet() 的不同之處

orElse() 和 orElseGet() 的不同之處在于前者傳入一個值,後者是個Lambda表達式。然而,如果傳入值的方式是通過調用其他方法形式就會産生一點細微差別,我們來解釋下什麼是調用其他方法形式傳值:

public class Test {
    public String functionA() {   //方法A
        return ..;
    }
    public void functionB(){    
        Optional opt = ...
        opt.orElse(functionA())   //orElse()調用方法A傳入值
    }
    public void functionC(){
        Optional opt = ...
        opt.orElseGet(()-> functionA())   //orElseGet調用方法A傳入值
    }
}
           

差別在與,如果傳入的value不為空,orElse()中的方法仍然要執行,而orElseGet() 不執行。

測試value屬性為空情況,二者都會執行:

@org.junit.Test
    public void testOrElseAndOrElseGet() {
        Optional<String> optional1 = Optional.ofNullable(null);    //value屬性為空
        Optional<String> optional2 = Optional.ofNullable(null); //value屬性為空

        optional1.orElse(getName());         //會執行getName()
        System.out.println("--------------");  
        optional2.orElseGet(() -> getName()); //會執行getName()
    }
    public String getName() {
       System.out.println("getName() executed !");     //列印執行辨別
       return "lisi";
    }
           

我們看下輸出:

getName() executed !   //orElse觸發列印的
--------------
getName() executed !   //orElseGet觸發列印的

           

測試value屬性不為空情況,orElse會執行,orElseGet不執行:

@org.junit.Test
    public void testOrElseAndOrElseGet2() {
        Optional<String> optional1 = Optional.ofNullable("lisi");  //value屬性不為空
        Optional<String> optional2 = Optional.ofNullable("lisi");  //value屬性不為空

        optional1.orElse(getName());    //會執行getName()
        System.out.println("--------------");
        optional2.orElseGet(() -> getName());   //不會執行getName()
    }
           

我們看下輸出:

getName() executed !
--------------
           
在執行較密集的調用時,比如調用 Web 服務或資料查詢,這個差異會對性能産生重大影響。

orElseThrow(Supplier<? extends X> exceptionSupplier)

它會在對象為空的時候抛出異常,而不是傳回某個值,并且該異常由入參傳入:

@Test(expected = IllegalArgumentException.class)  //期望抛IllegalArgumentException異常
    public void testOrElseThrow() {
        Optional.ofNullable(null).orElseThrow(() -> new IllegalArgumentException()); // 我們定義了一個空的執行個體,orElseThrow傳入一個異常
    }
           
這個方法讓我們有更豐富的語義,可以決定抛出什麼樣的異常,而不總是抛出 NullPointerException。

過濾值

filter(Predicate<? super T> predicate)

filter()方法作用是當Optional中value屬性不為空的時,對Optional中的值進行過濾。

注意:filter()方法傳回值仍是一個Optional對象,并且對象value屬性可能為空,而不是一個具體的值。

filter() 接受一個 Predicate 參數,這是個

判斷表達式

,判斷結果為true時,保留目前執行個體,判斷結果為false時,傳回一個空的Optional。

@org.junit.Test
 public void testFilter() {
     Optional<String> optional1 = Optional.ofNullable("abcd");  //非空執行個體
     Optional<String> optional2 = Optional.ofNullable(null);  //空執行個體

     //非空執行個體場景
     Assert.assertEquals(optional1.filter(a -> a.startsWith("ab")).get(), "abcd");      //表達式結果為true,保留目前執行個體
     Assert.assertEquals(optional1.filter(a -> a.startsWith("AB")), Optional.empty());   //表達式結果為fasle,傳回空執行個體
     
     //空執行個體場景,直接傳回空對象
     Assert.assertEquals(optional2.filter((a) -> (1 == 1)), Optional.empty());
 }
           

轉換值

map()和flatMap()對Optional中的對象進行轉換值的操作,這兩個方法唯一的差別就是接受的參數不同。

map()

對Optional中儲存的值進行函數運算,并傳回新的Optional(可以是任何類型)。入參是一個mapping函數,Optional的value屬性作為mapping函數入參。

注意:map()傳回值是Optional對象,mapper函數傳回值是任意值,最終會被封裝成Optional對象

我們看個例子:

@org.junit.Test
    public void testMap() {
        Optional<String> optional1 = Optional.ofNullable("ab");  // 非空執行個體
        Optional<String> optional2 = Optional.ofNullable(null); //空執行個體

        Assert.assertEquals(optional1.map(a -> a + "cd").get(), "abcd");   //函數作用是拼接字元串
        Assert.assertEquals(optional2.map(a -> a + "cd"), Optional.empty());   //空執行個體傳回空執行個體,不會調用函數表達式
    }
           

看下源碼:

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);   //傳入的函數必須存在,不能是null
        if (!isPresent())
            return empty();      //如果屬性值為空,直接傳回
        else {
            return Optional.ofNullable(mapper.apply(value));  //計算後的值,如果為空,通過ofNullable傳回empty()
        }
    }
           

由源碼,總結特點:

  • 傳入的函數不能為null,否則報NullPointerException

    Objects.requireNonNull(mapper)起的作用

  • 如果執行個體本身為空,不會執行函數,直接傳回空
  • 如果執行函數的計算結果為null,會傳回empty()

    對mapping傳回值調用ofNullable封裝起的作用

  • mapper函數傳回值是任意值,最終會被封裝成Optional對象

    對mapping傳回值調用ofNullable封裝起的作用

flatMap()

flatMap方法與map方法類似,也是對Optional中儲存的值進行函數運算,差別在于

mapping

函數的傳回值不同。

map()

方法的

mapping

函數傳回值可以是任何類型

T

,而

flatMap()

方法的

mapping

函數必須是

Optional

我們來看個例子,這個例子和map()非常相似,隻是對mapping傳回值進行封裝:

@org.junit.Test
  public void testFlatMap() {
  
      Optional<String> optional1 = Optional.ofNullable("ab"); //建構一個非空執行個體
      Optional<String> optional2 = Optional.ofNullable(null); // 建構一個空執行個體

      Assert.assertEquals(optional1.flatMap(a -> Optional.of(a + "cd")).get(), "abcd"); //接收一個mapping函數,進行拼接,并手動封裝成optional執行個體
      Assert.assertEquals(optional2.flatMap(a -> Optional.of(a + "cd")), Optional.empty());   //空執行個體直接傳回空執行個體

  }
           

驗證mapping傳回值不能為null:

@org.junit.Test(expected = NullPointerException.class)   //期望抛出異常
    public void testFlatMap_ReturnNotNull() {
        Optional<String> optional1 = Optional.ofNullable("ab");

        optional1.flatMap(a -> {
            return null;     //故意傳回null
        });
    }
           

看下源碼:

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();       //自身為空,直接傳回空Optional
        else {
            return Objects.requireNonNull(mapper.apply(value));    //mapping 傳回結果不能是null,可以是一個空的Optional執行個體
                              //由于mapping結果未經ofNullable封裝,是以需要mapping自身封裝一個Optional對象
        }
    }
           

由源碼,總結特點:

  • mapping傳回值是個Option對象

    flatMap最終傳回值是一個Option對象,由于mapping結果未經ofNullable封裝,是以需要mapping自身封裝一個Option對象。這是與map()最大差別的根本原因。

  • mapping傳回值不能是null

    由于mapping結果傳回值被Objects.requireNonNull檢查,

    是以格外注意傳回值一定不能是null,可以傳回一個空的Optional

  • 如果執行個體本身為空,不會執行函數,直接傳回空Optional

Optional 類的最佳實踐

經過上面的鋪墊,我們再來回顧開篇的問題,為何可以簡便的解決空指針問題?

User.java

public class User {
    private Address address;//省略get,set方法 
           

Address.java

public class Address {
    private Country country; //省略get,set方法 
           

Country.java

public class Country {
    private String isocode;//省略get,set方法 
           

TestOption.java

public String getIsoCode2(User user) {
      return Optional.ofNullable(user)
              .map(a -> a.getAddress())
              .map(a -> a.getCountry())
              .map(a -> a.getIsocode())
              .map(a -> a.toUpperCase())
              .orElse("CHINA");
  }
    @Test
    public void test() {
        //#1 user為null,直接越過中間步驟,根據orElse,得到預設值
        User user = null;
        Assert.assertEquals(getIsoCode(user), "CHINA");
        //#2 user非null,但是address屬性是null,直接越過中間步驟,根據orElse,得到預設值
        user = new User();
        Assert.assertEquals(getIsoCode(user), "CHINA");
        //#3 address屬性非null,但是country屬性是null,根據orElse,得到預設值
        Address address = new Address();
        user.setAddress(address);
        Assert.assertEquals(getIsoCode(user), "CHINA");
        //#4 country非null,那麼最終結果就是其值
        Country country = new Country();
        country.setIsocode("Usa");
        address.setCountry(country);
        Assert.assertEquals(getIsoCode(user), "USA");
    }
           

我們來分析一下上面的單元測試 #1步驟:

  • 執行 Optional.ofNullable(user)

    ofNullable對user進行封裝,得到一個Optional.empty執行個體

  • 執行 .map(a -> a.getAddress())

    由于此時Optional對象自身為空執行個體,map會直接傳回空執行個體

  • 執行.map(a -> a.getCountry())

    由于此時Optional對象自身為空執行個體,map會直接傳回空執行個體

  • 執行 .map(a -> a.getIsocode())

    由于此時Optional對象自身為空執行個體,map會直接傳回空執行個體

  • 執行 .map(a -> a.toUpperCase())

    由于此時Optional對象自身為空執行個體,map會直接傳回空執行個體

  • 執行 .orElse(“CHINA”);

    orElse作用是傳回一個值,這個是最終值,傳給return 語句,由于此時Optional對象自身為空執行個體,orElse傳回給定的預設值,即

    CHINA

簡單來說,通過map,把本來為null的屬性轉化為一個Optional.empty執行個體,在鍊式寫法中,空執行個體調用方法map()不會報錯,并且繼續獲的一個Optional.empty執行個體,直至鍊的尾部,通過orElse()收尾。

如果覺得對您有幫助,請不要吝惜您點贊和留言,呵呵,寫的蠻辛苦的。