NullPointerException,你們應該都見過。這是Tony Hoare在設計ALGOL W語言時提出的null引用的想法,他的設計初衷是想經過編譯器的自動檢測機制,確保全部使用引用的地方都是絕對安全的。不少年後,他對本身曾經作過的這個決定然後悔不已,把它稱爲「我價值百萬的重大失誤」。它帶來的後果就是---咱們想判斷一個對象中的某個字段進行檢查,結果發現咱們查看的不是一個對象,而是一個空指針,他會當即拋出NullPointerException異常。java
看下面這個例子:安全
public class Person { private Car car; public Car getCar() { return car; } } public class Car { private Insurance insurance; public Insurance getInsurance() { return insurance; } } public class Insurance { private String name; public String getName() { return name; } }
下面這個方法有什麼問題呢?函數
public String getCarInsuranceName(Person p){ return p.getCar().getInsurance().getName(); }
這是一個獲取保險公司名字的方法,可是在庫裏可能不少人沒有車,因此會返回null引用。更沒有車險,因此直接返回一個NullPointerException。工具
爲了不這種狀況,咱們通常會在須要的地方添加null的檢查,而且添加的方式每每不一樣。學習
避免NullPointerException第一次嘗試:spa
public String getCarInsuranceName(Person p){ if(p != null){ Car car = p.getCar(); if(car != null){ Insurance insurance = car.getInsurance(); if(insurance != null){ return insurance.getName(); } } } return "Unknown"; }
這個方法每次引用一個變量時,都會作一次null檢查,若是任何一個返回值爲null,則會返回Unknown。由於知道公司都必須有名字,因此最後一個保險公司的名字沒有進行判斷。這種方式不具有擴展性,同時還犧牲了代碼的可讀性。每一次都要嵌套一個if來進行檢查。設計
避免NullPointerException第二次嘗試:指針
public String getCarInsuranceName(Person p) { if (p == null) return "Unknown"; Car car = p.getCar(); if (car == null) return "Unknown"; Insurance insurance = car.getInsurance(); if (insurance == null) return "Unknown"; return insurance.getName(); }
第二種方式,避免了深層if語句塊,採用了每次遇到null都直接返回Unknown字符串的方式。而後這個方案也並不是理想,如今這個方法有了四個大相徑庭的退出點,使代碼的維護更艱難。發生null時的默認值,在三個不一樣的地方出現,不知道具體是哪一個返回null。code
Optional類orm
Java 8中引入了一個新的類java.util.Optional<T>。這是一個封裝Optional值的類。當變量存在時,Optional類知識對類簡單封裝,變量不存在時,缺失的值被建模成一個空的Optional對象,由方法Optional.empty()返回。該方法是一個靜態工廠方法,返回Optional類的特定單一實例。
null和Optional.empty()從語義上,能夠當作是一回事。實際上它們之間的差異很是大:若是你嘗試訪問一個null,必定會觸發null引用。而Optional.empty()能夠在任何地方訪問。
public class Person { private Optional<Car> car; public Optional<Car> getCar() { return car; } } public class Car { private Optional<Insurance> insurance; public Optional<Insurance> getInsurance() { return insurance; } }
公司的名字咱們沒有使用Optional<String> 而是保持了原類型String,那麼它就必須設置一個值。
建立Optional對象
1.聲明一個空的Optional
Optional<Car> car = Optional.empty();
2.依據一個非空值建立Optional
Car car = new Car(); Optional<Car> optCar = Optional.of(car);
若是car是null,則直接會報錯null引用,而不是等到你訪問時。
3.可接受null的Optional,這種方式與of不一樣,編譯器運行時不會報錯。
Car car = null; Optional<Car> optCar = Optional.ofNullable(car);
使用map從Optional對象中提取和轉換值
從對象中讀取信息是一種比較常見的模式。好比,你能夠從insurance公司對象中提取公司的名稱。提取名稱以前你須要檢查insurance對象是否爲null,如:
String name = null; if(insurance != null){ name = insurance.getName(); }
爲了支持這種模式,Optional提供了一個map方法。
Optional<Insurance> optionalInsurance = Optional.ofNullable(insurance);
Optional<String> name = optionalInsurance.map(Insurance::getName);
這裏的map和流中的map相差無幾。map操做會將提供的函數應用於流的每一個元素。你能夠把Optional對象當作一種特殊的集合數據。如圖:
這看齊來挺有用,可是如何應用起來,重構以前的代碼呢?
p.getCar().getInsurance().getName();
使用flatMap連接Optional對象
使用剛剛的學習的map,第一反應是重寫以前的代碼,好比這樣:
Optional<Person> person = Optional.of(p); Optional<String> name = person .map(Person::getCar) .map(Car::getInsurance) .map(Insurance::getName);
可是這段代碼沒法經過編譯,person是Optional<Person>類型的變量,調用map方法沒有問題,可是getCar返回的是一個Optional<Car>類型的對象,這意味着map操做的結果的結果是一個Optional<Optinoal<Car>>類型的對象。 所以它調用getInsurance是非法的。
在流中使用flatMap能夠扁平化合並流,在這裏你想把兩層的Optional合併爲一個。
public String getCarInsuranceName(Person p) { Optional<Person> person = Optional.of(p); return person .flatMap(Person::getCar) .flatMap(Car::getInsurance) .map(Insurance::getName) .orElse("Unknown"); }
經過代碼的比較,處理潛在可能缺失的值時,使用Optional具備明顯的優點。你能夠很是容易實現指望的效果,不須要寫那麼多的條件分支,也不會增長代碼的複雜性。
首先,Optional.of(p) 生成Optional<person>對象,而後調用person.flatMap(Person::GetCar)返回一個Optional<Car> 對象,Optional內的Person也被轉換成了這種對象,結果就是兩層的Optional對象,最終他們會被flatMap操做合併起來。若是合併時其中有一個爲空,那麼就構成一個空的Optional對象。若是給一個空的Optional對象調用flatMap返回的也是空的Optional對象。
而後,flatMap(Car::getInsurance) 會轉換成Optional<Insurance> 合併。 第三步 這裏調用的是map方法,由於返回類型是string 就不須要flatMap了。若是連上的任何一個結果爲空就返回空,不然返回的值就是指望的值。 因此最後用了一個orElse的方法,當Optional爲空的時候返回一個默認值。
獲取Optional對象的值:
1. get() 是這些方法中最簡單但最不安全的方法。若是變量存在,直接返回封裝的變量值。不然拋出一個NoSuchElementException異常。
2. orElse(T other) 默認值,當值存在返回值,不然返回此默認值。
3. orElseGet(Supplier<? extends T> other) 是orElse方法的延遲調用版,Supplier方法只有在Optional對象不含值時才執行調用。
4. orElseThrow(Supplier<? extends X> exceptionSupplier )和get方法類似,遇到Optional對象爲空時都拋出一個異常,使用orElseThrow能夠自定義異常類型。
5. ifPresent(Consumer<? super T>) 在變量值存在時執行,不然什麼都不作。
判斷Optional是否有值 isPresent()
假設有一個方法,接受兩個參數 Person 和Car 來查詢便宜的保險公司:
public Insurance getInsurance(Person person ,Car car){ //業務邏輯 return new Insurance(); }
這是之前的版本,使用咱們今天所學的知識 能夠作一個安全版本,它接受兩個Optional對象做爲參數 返回值也是一個Optional<Insurance>方法:
public static Optional<Insurance> getInsuranceOpt(Optional<Person> person,Optional<Car> car){ if(person.isPresent() && car.isPresent()){ return Optional.of(getInsurance(person.get(),car.get())); } return Optional.empty(); }
這看起來好了不少,更優雅的方式:
public static Optional<Insurance> getInsuranceOpt1(Optional<Person> person, Optional<Car> car) { return person.flatMap(p -> car.map(c -> getInsurance(p, c))); }
若是p爲空,不會執行返回空的Optional對象。若是car爲空也不會執行 返回空Optional對象。 若是都有值那麼調用這個方法。
filter剔除特定的值
除了map和flatMap方法相似流中的操做,還有filter方法。使用filter能夠快速判斷Optional對象中是否包含指定的規則,如:
Insurance insurance = new Insurance(); if(insurance != null && insurance.getName().equals("abc")){ System.out.println("is abc"); }
可使用filter改寫爲:
Optional<Insurance> insuranceOpt = Optional.of(insurance);
insuranceOpt.filter(c->c.getName().equals("abc")).ifPresent(x->System.out.println(x));
用Optional改善你的代碼
咱們雖然很難對老的Java API進行改動,可是能夠再本身的代碼中添加一些工具方法,來修復或者繞過這些問題,容納給你的代碼享有Optional帶來的威力。
使用Optional封裝可能爲null的值
現存的Java API幾乎都是經過返回一個null的方式來表示須要值的缺失,或者因爲某些緣由計算沒法獲得該值。好比,若是Map中不含指定的鍵對應的值,它的get就會返回一個null。咱們想在這種狀況下返回Optional對象是很容易的。
Object value = new HashMap<String,Object>().get("key"); //null
有兩種方式轉換爲Optional對象,第一種就是if else 方式,顯然很笨重。第二種就是使用ofNullable方法。
Optional<Object> value = Optional.ofNullable(new HashMap<String,Object>().get("key"));
每次你但願安全的對潛在爲null的對象進行轉換時,均可以先將其轉換爲Optional對象。
異常與Optional
因爲某種緣由,函數沒法返回某個值,這時除了返回null,還會拋出一個異常。典型的例子是Integer.parseInt(String),將String轉換爲int。若是String沒法解析爲整型,就會拋出NumberFormatException異常。通常作這個操做,咱們會加入 try/catch來避免程序掛掉,而不是用if來判斷。
使用Optional對象對遭遇沒法轉換的String返回非法值進行建模,這時你指望parseInt的返回值是一個optional。雖然咱們沒法改變之前的方法,但咱們能夠建立一個工具方法:
public static Optional<Integer> StringToInt(String s){ try{ return Optional.of(Integer.parseInt(s)); }catch (Exception ex){ return Optional.empty(); } }
咱們能夠創建一個OptionalUtils工具類,而後對全部的相似轉換操做建立方法。而後在須要的地方 OptionalUtils.StringToInt(Stirng);
基礎類型的Optional對象
與Stream對象同樣,Optional對象也提供了相似的基礎類型:OptionalInt、OptionalDouble、OptionalLong。 可是這三個基礎類型不支持map、flatMap、filter方法。
小結:
1.null引用在歷史上被引入到程序設計語言中,目的是爲了表示變量值的缺失。
2.Java 8中加入了一個新的類 java.util.Optional<T> 對存在或缺失的變量進行建模。
3.你可使用靜態工廠方法Optional.empty、Optional.of、Optional.ofNullable建立Optional對象。
4.Optional支持多種方法,好比map、flatMap、filter,他們在概念上與Stream相似。
5.使用Optional會迫使你更積極的引用Optional對象,以及應對變量缺失的問題,最終你能更有效的防治代碼中出現空指針異常。
6.使用Optional能幫助你更好的設計API,用戶只須要參閱簽名酒知道該方法是否接受一個Optional。