Dating Java8系列之巧用Optional之優雅規避NPE問題

翎野君/文

 

圖片

 

圖片

避之不及的 NullPointerException

 

NPE : NullPointerException

空指針異常是最常見的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; }}

如果我們此時,需要獲取一個用戶對應的汽車保險名稱,我們可能會寫出來以下的代碼

private String getInsuranceName(User user) {    return user.getCar().getInsurance().getInsuranceName();}

 

圖片

避免NullPointerException的方法

 

顯然上面的程序是存在諸多NullPointerException隱患的,爲了保證程序的健壯性,我們需要儘量避免出現空指針NullPointerException,那麼通常我們會有以下兩種寫法。

 

1.深層質疑

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";    }

2.及時退出

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值的類。舉例來說,使用新的類意味着,如果你知道一個人可能有也可能沒有車,那麼Person類內部的car變量就不應該聲明爲Car,遇到某人沒有車時把null引用值給它後就有可能會出現空指針的問題,應該如下圖所示直接將其聲明爲Optional類型。

圖片

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

 

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

從語義上,你可以把它們當作一回事兒,但是實際中它們之間的差別非常大:如果你嘗試直接引用一個null,一定會觸發NullPointerException,不過使用 Optional.empty()就完全沒事兒,它是Optional類的一個有效對象。

使用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是如何豐富建模時的變量語義了吧。代碼中person引用的是Optional<Car>,這種方式非常清晰地表達了你的模型中一個person 可能有也可能沒有car的情形,同樣,car可能進行了保險,也可能沒有保險。

與此同時,我們看到insurance的名稱insuranceName被聲明成String類型,而不是Optional,這非常清楚地表明聲明爲insurance的類中的名稱字段insuranceName是必須存在的。所以,如果你遇到一個insurance沒有名稱,出現空指針異常的時候,你需要調查你的數據出了什麼問題,而不應該再添加一段代碼,將這個問題隱藏。

使用這種方式, 一旦通過引用insurance獲取insuranceName時發生NullPointerException,你就能非常確定地知道出錯的原因,不再需要爲其添加null的檢查查,因爲null的檢查查只會掩蓋問題,並未真正地修復問題。 

 

圖片

Optional的方法介紹

 

1.創建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的方法。

 

2.用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();

3.用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(user).orElse(new User()).getUserName();

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

String carName = Optional.ofNullable(user).map(u -> u.getCar()).map(c -> c.getCarName()).orElse(null);

將用戶名轉爲大寫

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

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

Optional<String> filterOptional = Optional.ofNullable("張三").filter(value -> Objects.equals(value, "張三"));

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

Optional.ofNullable(user).filter(user1 -> Objects.equals(user1.getUserName(), "張三")).ifPresent(user1 -> {user1.setUserName("李四");System.out.println(user.getUserName());});

 

作者:翎野君
博客:https://www.cnblogs.com/lingyejun/

 

本篇文章如有幫助到您,請給「翎野君」點個贊,感謝您的支持。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章