可怕的 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>類型。
變量存在時,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沒有名稱,你需要調查你的數據出了什麼問題,而不應該再添加一段代碼,將這個問題隱藏。