Java8 中的 Optional 類的基本使用

Java8 引入了一個十分有趣的 Optional 類它主要是爲了解決臭名昭著的空指針異常(NullPointerException)。當咱們對對象的屬性進行檢查,判斷它的值是否爲指望的格式,最終卻發現咱們查看的並非一個對象,而是一個空指針,它會當即拋出一個讓人厭煩的 NullPointerException 異常。java

拋磚

咱們來看一個簡單的實例:函數

String address = world.getCountry.getCity.getName;
複製代碼

在獲得地址以前,須要對各個類進行檢查防止出現空指針異常:spa

public String getAddress(World world){
        if (world != null){
            Country country = world.getCountry();
            if (country!=null){
                City city = country.getCity();
                if (city != null){
                    return city.getName();
                }
            }
        }

        return "unknown";
    }
複製代碼

能夠看到上面的檢查有多麼繁雜,代碼中充斥着空檢查,可讀性糟糕透頂。設計

Optional 類入門

變量存在時, Optional 類只是對類簡單封裝。變量不存在時,缺失的值會被建模成一個「空」 的 Optional 對象,由方法 Optional.empty() 返回。 那你可能就會疑惑,null 和 Optional.empty() 的區別在哪呢?從語義上,你能夠把它們看成一回事兒,可是實際中它們之間的差異很是 大 : 若是你嘗試解引用一個 null , 必定會觸發NullPointerException , 不過使用 Optional.empty() 就徹底沒事兒,它是 Optional 類的一個有效對象,多種場景都能調用,很是有用。3d

應用 Optional 的幾種模式

建立 Optional 對象實例

能夠建立一個空的 Optional 對象實例指針

@Test(expected = NoSuchElementException.class)
    public void createOptionalObject(){
        Optional<String> country = Optional.empty();
        country.get();
    }
複製代碼

毫無疑問,當咱們調用 get() 方法會報 NoSuchElementException 異常code

還可使用 of() 和 ofNullable() 方法建立包含值的 Optioanal 實例,區別在於若是將 null 看成參數傳進去 of() 會報空指針異常,因此對象可能存在或者不存在,應該使用 ofNullable()cdn

@Test
    public void createOptionalObject(){
        Optional<String> country = Optional.of("中國");
        Optional<String> city = Optional.ofNullable("上海");
        Optional<String> world = Optional.ofNullable(null);
        //下面會報空指針異常
        Optional<String> province = Optional.of(null);
    }
複製代碼

如何獲取Optional變量中的值 ?Optional 提供了一個 get() 方法。不過 get方法在遭遇到空的Optional對象時也會拋出異常,因此不按照約定的方式使用它,又會讓咱們再度陷入由null引發的代碼維護的夢魘。對象

訪問 Optional 對象的值

從 Optional 實例中取回實際值對象的方法之一是使用 get() 方法:blog

@Test
    public void getOptionalObject(){
        String country = "China"
        Optional<String> countryName = Optional.ofNullable(country);
        
        Assert.assertEquals("China",countryName.get());
    }
複製代碼

固然這個方法會在值爲null時拋出異常,要避免異常,首先要進行檢查

@Test
    public void getOptionalObject(){
        City city = new City("ShangHai");
        Optional<City> sample = Optional.ofNullable(city);
        Assert.assertTrue(sample.isPresent());

        Assert.assertEquals(city.getName(),sample.get().getName());
    }
複製代碼

檢查是否有值還有另一個方法 ifPresent(),該方法除了檢查還會傳入一個 Consumer(消費者) 參數,若是對象不是空的,就會執行傳入的 Lambda 表達式

@Test
    public void getOptionalObject(){
        City city = new City("ShangHai");
        Optional<City> sample = Optional.ofNullable(city);
        sample.ifPresent(c -> Assert.assertEquals(city.getName(),sample.get().getName()));
    }
複製代碼

若是對象不爲空則爲執行斷言

返回默認值

Optional 提供了 API 用以返回對象值,或者在對象爲空的時候返回默認值

@Test
    public void getOptionalObject(){
        City city = null;
        City city1 = new City("ShangHai");
        City sample = Optional.ofNullable(city).orElse(city1);
        Assert.assertEquals(city1.getName(),sample.getName());
    }
複製代碼

第二個同類型的 API 是 orElseGet() —— 其行爲略有不一樣。這個方法會在有值的時候返回值,若是沒有值,它會執行做爲參數傳入的 Supplier(供應者) 函數式接口,並將返回其執行結果:

City sample = Optional.ofNullable(city).orElseGet(() -> city1);
複製代碼

返回異常

Optional 還定義了 orElseThrow() API 它會在對象爲空時拋出異常

@Test(expected = IllegalArgumentException.class)
    public void throwOptionalException(){
        City city = null;
        City sample = Optional.ofNullable(city).orElseThrow(() -> new IllegalArgumentException());
    }
複製代碼

city 爲空因此會拋出 IllegalArgumentException

這個方法讓咱們有更豐富的語義,能夠決定拋出什麼樣的異常,而不老是拋出 NullPointerException。

使用 Optional 的實戰實例

使用map從 Optional 對象中提取和轉換值

從對象中提取信息是一種比較常見的模式,爲了支持這種模式,Optional提供了一個map方法。它的工做方式以下:

@Test
    public void getCityName(){
        City city = new City("ShangHai");
        Optional<City> sample = Optional.ofNullable(city);
        Optional<String> name = sample.map(City::getName);
    }
複製代碼

map 對值應用(調用)做爲參數的函數,而後將返回的值包裝在 Optional 中,這就使對返回值進行鏈試調用的操做成爲可能,那是否是就能夠對以前的代碼進行重構呢?

public Optional<String> getCityName(World world){

        Optional<World> real = Optional.ofNullable(world);
        Optional<String> name =
                real.map(World::getCountry)
                    .map(Country::getCity)
                    .map(City::getName);

        return name;
    }
複製代碼

可是這段代碼沒法經過編譯,real.map(World::getCountry) 返回的是 Optional 的實例這個沒有問題,可是後面繼續調用map產生的就是 Optional<Optional>類型的對象。說明你遭遇了嵌套式的 Optional 機構。

optional

兩層Optional對象結構

使用 flatMap 連接 Optional 對象

因此,咱們該如何解決這個問題呢?讓咱們再回顧一下你剛剛在流上使用過的模式: flatMap 方法。使用流時, flatMap 方法接受一個函數做爲參數,這個函數的返回值是另外一個流。 這個方法會應用到流中的每個元素,最終造成一個新的流的流。可是 flagMap 會用流的內容替 換每一個新生成的流。換句話說,由方法生成的各個流會被合併或者扁平化爲一個單一的流。這裏 你但願的結果其實也是相似的,可是你想要的是將兩層的 optional 合併爲一個。

public Optional<String> getCityName(World world){

        Optional<World> real = Optional.ofNullable(world);
        Optional<String> name =
                real.flagMap(World::getCountry)
                    .flagMap(Country::getCity)
                    .map(City::getName);

        return name;
    }
複製代碼

使用 filter 剔除特定的值

你常常須要調用某個對象的方法,那麼你首先要檢查對象是否爲NULL

public void filterCity(City city){

    Optional<City> real = Optional.ofNullable(city);
    real.filter(c -> c!=null && "ShangHai"
            .equals(c.getName()))
            .ifPresent(x -> System.out.println("ok"));

}
複製代碼

小結

  1. null 引用在歷史上被引入到程序設計語言中,目的是爲了表示變量值的缺失。
  2. Java 8中引入了一個新的類 java.util.Optional,對存在或缺失的變量值進行 建模。
  3. 你可使用靜態工廠方法 Optional.empty、 Optional.of 以及 Optional.ofNullable 建立 Optional 對象。
  4. Optional類支持多種方法,好比 map、 flatMap、 filter,它們在概念上與 Stream 類中對應的方法十分類似。
  5. 使用 Optional 會迫使你更積極地解引用 Optional 對象,以應對變量值缺失的問題,最終,你能更有效地防止代碼中出現不期而至的空指針異常。
  6. 使用 Optional 能幫助你設計更好的 API,用戶只須要閱讀方法簽名,就能瞭解該方法是否接受一個 Optional類型的值。
相關文章
相關標籤/搜索