避之不及的 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/
本篇文章如有幫助到您,請給「翎野君」點個贊,感謝您的支持。