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.empty() 返回。 那你可能就會疑惑,null 和 Optional.empty() 的區別在哪呢?從語義上,你能夠把它們看成一回事兒,可是實際中它們之間的差異很是 大 : 若是你嘗試解引用一個 null , 必定會觸發NullPointerException , 不過使用 Optional.empty() 就徹底沒事兒,它是 Optional 類的一個有效對象,多種場景都能調用,很是有用。3d
能夠建立一個空的 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 實例中取回實際值對象的方法之一是使用 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方法。它的工做方式以下:
@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對象結構
因此,咱們該如何解決這個問題呢?讓咱們再回顧一下你剛剛在流上使用過的模式: 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;
}
複製代碼
你常常須要調用某個對象的方法,那麼你首先要檢查對象是否爲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"));
}
複製代碼