【Java 8】用Option代替Null

可怕的 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,那麼通常我們會有以下兩種寫法。

深層質疑

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類來優化這種寫法。

Option入門

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

clipboard.png

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

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

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

使用Optional而不是null的一個非常重要而又實際的語義區別是,第一個例子中,我們在聲明變量時使用的是Optional<Car>類型,而不是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>, 而car引用的是Optional<Insurance>,這種方式非常清晰地表達了你的模型中一個person 可能有也可能沒有car的情形,同樣,car可能進行了保險,也可能沒有保險。

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

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

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

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