Java 8 (9) Optional取代null

  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。

相關文章
相關標籤/搜索