引言
對任何一位Java程序員來說,NullPointerException應該都遇到過。而且很多時候,線上出現問題,就是因爲這個錯,往往是由於我們判斷遺漏。下面從一個簡單的實例開始說起。
實例
public class Person { private Car car; public Car getCar() { return car; } }
public class Car { private Insurance insurance; public Insurance getInsurance() { return insurance; } }
public class Insurance { private String name; public String getName() { return name; } }
上述代碼看起來相當正常,但是現實生活中很多人沒有車。所以調用getCar方法的結果會怎樣呢?
在實踐中,一種比較常見的做法是返回一個null引用,表示該值的缺失,即用戶沒有車。
而接下來,對getInsurance的調用會返回null引用的insurance,這會導致運行時出現一個NullPointerException,終止程序的運行。
但這還不是全部。如果返回的person值爲null會怎樣?如果getInsurance的返回值也是null,結果又會怎樣?
採用防禦式檢查減少NullPointerException
怎樣做才能避免這種不期而至的NullPointerException呢?通常,我們會在需要的地方添加null的檢查
- 深層質疑
public String getCarInsuranceName(Person person) {
if (person != null) {
Car car = person.getCar();
if (car != null) {
Insurance insurance = car.getInsurance();
if (insurance != null) {
return insurance.getName();
}
}
}
return "Unknown";
}
這個方法每次引用一個變量都會做一次null檢查,如果引用鏈上的任何一個遍歷的解變量值爲null,它就返回一個值爲“Unknown”的字符串。
上述代碼它不斷重複着一種模式:每次不確定一個變量是否爲null時,都需要添加一個進一步嵌套的if塊,也增加了代碼縮進的層數。很明顯, 這種方式不具備擴展性,同時還犧牲了代碼的可讀性。
- 過多的退出語句
public String getCarInsuranceName(Person person) {
if (person == null) {
return "Unknown";
}
Car car = person.getCar();
if (car == null) {
return "Unknown";
}
Insurance insurance = car.getInsurance();
if (insurance == null) {
return "Unknown";
}
return insurance.getName();
}
上述代碼避免深層遞歸的if語句塊,採用了一種不同的策略:每次遇到null變量,都返回一個字符串常量“Unknown”。
這種方案遠非理想,現在這個方法有了四個截然不同的退出點,使得代碼的維護異常艱難。進一步而言,這種流程是極易出錯的;如果你忘記檢查了那個可能爲null的屬性會怎樣?
null的種種問題
- NullPointerException是目前Java程序開發中典型的異常
- 它讓代碼充斥着深度嵌套的null檢查,代碼的可讀性糟糕透頂。
- null自身沒有任何的語義,尤其是,它代表的是在靜態類型語言中以一種錯誤的方式對缺失變量值的建模。
- Java一直試圖避免讓程序員意識到指針的存在,唯一的例外是:null指針。
- null並不屬於任何類型,這意味着它可以被賦值給任意引用類型的變量。這會導致問題, 原因是當這個變量被傳遞到系統中的另一個部分後,你將無法獲知這個null變量初的賦值到底是什麼類型。
Optional 類
Java 8中引入了一個新的類java.util.Optional。這是一個封裝Optional值的類。變量存在時,Optional類只是對類簡單封裝。變量不存在時,缺失的值會被建模成一個“空” 的Optional對象,由方法Optional.empty()返回。
重新定義上述Person、Car、Insurance類,代碼如下:
public class Person { private Optional<Car> car; public Optional<Car> getCar() { return car; } }
public class Car { private Optional<Insurance> insurance; public Optional<Insurance> getInsurance() { return insurance; } }
public class Insurance { private String name; public String getName() { return name; } }
代碼中person引用的是Optional<Car>
, 而car引用的是Optional<Insurance>
,這種方式非常清晰地表達了一個person可能擁有也可能沒有car的情形,同樣,car可能進行了保險,也可能沒有保險。
應用Optional
(1)創建Optional對象
- 聲明一個空的Optional
Optional<Car> optCar = Optional.empty();
- 依據一個非空值創建Optional
Optional<Car> optCar = Optional.of(car);
如果car是一個null,這段代碼會立即拋出一個NullPointerException,而不是等到試圖訪問car的屬性值時才返回一個錯誤。
- 可接受null的Optional
Optional<Car> optCar = Optional.ofNullable(car);
如果car是null,那麼得到的Optional對象就是個空對象。
(2)使用map 從 Optional 對象中提取和轉換值
從對象中提取信息是一種比較常見的模式。比如,從insurance公司對象中提取公司的名稱。提取名稱之前,你需要檢查insurance對象是否爲null,代碼如下所示:
String name = null;
if(insurance != null){
name = insurance.getName();
}
爲了支持這種模式,Optional提供了一個map方法:
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
(3)使用flatMap 鏈接 Optional 對象
- 使用Optional獲取car的保險公司名稱
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}
由於包含嵌套的Optional,使用map無法通過編譯,藉助flatMap可以獲取引用對象的值。
(4)使用filter剔除特定的值
在實際項目中,我們會經常遇到以下代碼場景:
Insurance insurance = ...;
if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
System.out.println("ok");
}
使用Optional對象的filter方法,這段代碼可以重構如下:
Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance ->"CambridgeInsurance".equals(insurance.getName())).ifPresent(x -> System.out.println("ok"));
Optional類方法
方法 | 描述 |
---|---|
empty | 返回一個空的 Optional 實例 |
filter | 如果值存在並且滿足提供的謂詞,就返回包含該值的 Optional 對象;否則返回一個空的 Optional 對象 |
flatMap | 如果值存在,就對該值執行提供的 mapping函數調用,返回一個 Optional 類型的值,否則就返 回一個空的 Optional 對象 |
get | 如果該值存在,將該值用 Optional 封裝返回,否則拋出一個 NoSuchElementException 異常 |
ifPresent | 如果值存在,就執行使用該值的方法調用,否則什麼也不做 |
isPresent | 如果值存在就返回 true,否則返回 false |
map | 如果值存在,就對該值執行提供的 mapping函數調用 |
of | 將指定值用 Optional 封裝之後返回,如果該值爲 null,則拋出一個 NullPointerException 異常 |
ofNullable | 將指定值用 Optional 封裝之後返回,如果該值爲 null,則返回一個空的 Optional 對象 |
orElse | 如果有值則將其返回,否則返回一個默認值 |
orElseGet | 如果有值則將其返回,否則返回一個由指定的 Supplier 接口生成的值 |
orElseThrow | 如果有值則將其返回,否則拋出一個由指定的 Supplier 接口生成的異常 |
總結
引入Optional 類的意圖並非要消除每一個null引用。與此相反,它的目標是幫助你更好地設計出普適的API, 讓程序員看到方法簽名,就能瞭解它是否接受一個Optional的值。