【Java 8】巧用Optional之優雅規避NPE問題

避之不及的 NullPointerException

NPE : NullPointerExceptionjava

空指針異常是最多見的Java異常之一,拋出NPE錯誤不是用戶操做的錯誤,而是開發人員的錯誤,應該被避免,那麼只能在每一個方法中加入非空檢查,閱讀性和維護性都比較差。程序員

如下是一個常見的嵌套對象:一個用戶所擁有的汽車,以及爲這個汽車配備的保險。安全

public class User {

    private String userName;

    private Car car;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }
}

public class Car {

    private String carName;

    private Insurance insurance;

    public String getCarName() {
        return carName;
    }

    public void setCarName(String carName) {
        this.carName = carName;
    }

    public Insurance getInsurance() {
        return insurance;
    }

    public void setInsurance(Insurance insurance) {
        this.insurance = insurance;
    }
}

public class Insurance {

    private String insuranceName;

    public String getInsuranceName() {
        return insuranceName;
    }

    public void setInsuranceName(String insuranceName) {
        this.insuranceName = insuranceName;
    }
}
複製代碼

若是咱們此時,須要獲取一個用戶對應的汽車保險名稱,咱們可能會寫出來如下的代碼bash

private String getInsuranceName(User user) {
    return user.getCar().getInsurance().getInsuranceName();
}
複製代碼

避免NullPointerException的方法

顯然上面的程序是存在諸多NullPointerException隱患的,爲了保證程序的健壯性,咱們須要儘可能避免出現空指針NullPointerException,那麼一般咱們會有如下兩種寫法。app

深層質疑

private String getInsuranceName(User user) {
        if (user != null) {
            Car car = user.getCar();
            if (car != null) {
                Insurance insurance = car.getInsurance();
                if (insurance != null) {
                    return insurance.getInsuranceName();
                }
            }
        }
        return "not found";
    }
複製代碼

及時退出

private String getInsuranceName(User user) {
        if (user == null) {
            return "not found";
        }
        Car car = user.getCar();
        if (car == null) {
            return "not found";
        }
        Insurance insurance = car.getInsurance();
        if (insurance == null) {
            return "not found";
        }
        return insurance.getInsuranceName();
    }
複製代碼

爲了不出現空指針,咱們一般會採用以上兩種寫法,可是它們複雜又冗餘,爲了鼓勵程序員寫更乾淨的代碼,代碼設計變得更加的優雅。JAVA8提供了Optional類來優化這種寫法。函數

Optional

Optional入門

Java 8中引入了一個新的類java.util.Optional。這是一個封裝Optional值的類。舉例來講,使用新的類意味着,若是你知道一我的可能有也可能沒有車,那麼User類內部的car變量就不該該聲明爲Car, 遇某人沒有車時把null引用值給它,而是應該以下圖所示直接將其聲明爲Optional類型。優化

opp

變量存在時,Optional類只是對類簡單封裝。變量不存在時,缺失的值會被建模成一個「空」 的Optional對象,由方法Optional.empty()返回。它返回Optional類的特定單一實例。ui

null引用和Optional.empty() 有什麼本質的區別嗎?this

從語義上,你能夠把它們看成一回事兒,可是實際中它們之間的差異很是大:若是你嘗試直接引用一個null,必定會觸發NullPointerException,不過使用 Optional.empty()就徹底沒事兒,它是Optional類的一個有效對象。spa

使用Optional而不是null的一個很是重要而又實際的語義區別是,第一個例子中,咱們在聲明變量時使用的是Optional類型,而不是Car類型,這句聲明很是清楚地代表了這裏發生變量缺失是容許的。與此相反,使用Car這樣的類型,可能將變量賦值爲null,你只能依賴你對業務模型的理解,判斷一個null是否屬於該變量的有效值又或是異常狀況。

public class User {

    private String userName;

    private Optional<Car> car;

    public String getUserName() {
        return userName;
    }
    public Optional<Car> getCar() {
        return car;
    }
}
public class Car {
    private String carName;
    private Optional<Insurance> insurance;
    public String getCarName() {
        return carName;
    }
    public Optional<Insurance> getInsurance() {
        return insurance;
    }
}
public class Insurance {
    private String insuranceName;
    public String getInsuranceName() {
        return insuranceName;
    }
}
複製代碼

發現Optional是如何 富你模型的語義了吧。代碼中user引用的是Optional, 而car引用的是Optional,這種方式很是清晰地表達了你的模型中一個user 可能有也可能沒有car的情形,一樣,car可能進行了保險,也可能沒有保險。

與此同時,咱們看到insurance的名稱insuranceName被聲明成String類型,而不是Optional,這很是清楚地代表聲明爲insurance的類中的名稱字段insuranceName是必須存在的。

使用這種方式, 一旦經過引用insurance獲取insuranceName時發生NullPointerException,你就能很是肯定地知道出錯的緣由,再也不須要爲其添加null的檢查查,由於null的檢查查只會掩蓋問題,並未真正地修復問題。

insurance必須有個名字,因此,若是你遇到一個insurance沒有名稱,你須要調查你的數據出了什麼問題,而不該該再添加一段代碼,將這個問題隱藏。

Optional的方法介紹

建立Optional

of(T value)

若是構造參數是一個null,這段代碼會當即 出一個NullPointerException,而不是等到你 圖訪問car的屬性值時才返回一個錯誤。

public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}
複製代碼

ofNullable(T value)

建立一個容許null值的Optional對象

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}
複製代碼

empty()

建立一個空的Optional對象

public static<T> Optional<T> empty() {
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
複製代碼

經常使用方法

  • get()是這些方法中最簡單但又最不安全的方法。若是變量存在,它直接返回封裝的變量值,不然就拋出一個NoSuchElementException異常。因此,除非你很是肯定Optional變量必定包含值,不然最好不要使用這個方法。
  • orElse(T other),它容許你在 Optional對象不包含值時提供一個默認值。
  • orElseGet(Supplier<? extends T> other)是orElse方法的延遲調用版,Supplier方法只有在Optional對象不含值時才執行調用。
  • orElseThrow(Supplier<? extends X> exceptionSupplier)和get方法很是相似,它們遭遇Optional對象爲空時都會拋出一個異常,可是使用orElseThrow你能夠定製但願拋出的異常類型。
  • ifPresent(Consumer<? super T>)讓你能在變量值存在時執行一個做爲參數傳入的方法,不然就不進行任何操做。

注意:orElse(T other)和orElseGet(Supplier<? extends T> other)的區別

這兩個函數的區別:當value值不爲null時,orElse函數依然會執行返回T的方法,而orElseGet函數並不會執行返回T的方法。

用map從Optional中提取和轉換值

map(Function<? super T, ? extends U> mapper)

能夠把Optional對象當作一種特殊的集合數據,它至多包含一個元素。若是Optional包含一個值,那函數就將該值做爲參數傳遞給map,對該值進行轉換。若是Optional爲空,就什麼也不作。

String optionMap = Optional.ofNullable("abc").map(value -> value.toUpperCase()).get();
複製代碼

使用flatMap連接Optional對象

flatMap(Function<? super T, Optional> mapper)

將兩層的optional合併爲一個

String optionFlatMap = Optional.ofNullable("abc").flatMap(value -> Optional.of((value + "flat-map").toUpperCase())).get();
複製代碼

用filter剔除特定的值

filter(Predicate<? super T> predicate)

filter方法接受一個謂詞做爲參數。若是Optional對象的值存在,而且它符合謂詞的條件, filter方法就返回其值;不然它就返回一個空的Optional對象。

Optional<String> filterOptional = Optional.ofNullable("abc").filter(value -> Objects.equals(value, "abc"));
複製代碼

實戰

嘗試獲取用戶的用戶名稱,不存在則返回默認值

String userName = Optional.ofNullable(userOfNull).orElse(new User()).getUserName();
複製代碼

嘗試獲取用戶的carName,不存在則返回null

String carName = Optional.ofNullable(userOfNull).map(u -> u.getCar()).map(c -> c.getCarName()).orElse(null);
複製代碼

用戶名存在的時候轉爲大寫

Optional.ofNullable(user).map(u -> u.getUserName()).ifPresent(userName -> System.out.println(userName.toUpperCase()));
複製代碼

過濾出來用戶名稱是張三的用戶

Optional.ofNullable(user).filter(u -> Objects.equals(u.getUserName(),"張三")).map(u -> u.getUserName()).ifPresent(userName -> System.out.println(userName + "實戰Test"));
複製代碼

將張三的用戶名稱更改成李四

Optional.ofNullable(user).ifPresent(x -> {
            if (Objects.equals(user.getUserName(),"張三")){
                user.setUserName("李四");
            }
        });

Optional.ofNullable(user).filter(u -> Objects.equals(user.getUserName(),"張三")).ifPresent(x -> user.setUserName("李四"));
複製代碼
相關文章
相關標籤/搜索