一、簡介
到目前為止贮缅,臭名昭著的空指針異常是導(dǎo)致Java應(yīng)用程序失敗的最常見原因。以前介却,為了解決空指針異常谴供,Google公司著名的Guava項(xiàng)目引入了Optional類,Guava通過使用檢查空值的方式來防止代碼污染齿坷,它鼓勵(lì)程序員寫更干凈的代碼桂肌。受到Google Guava的啟發(fā),Optional類已經(jīng)成為Java 8類庫的一部分永淌。
Optional實(shí)際上是個(gè)容器:它可以保存類型T的值崎场,或者僅僅保存null。Optional提供很多有用的方法遂蛀,這樣我們就不用顯式進(jìn)行空值檢測谭跨。
此處引用java 8實(shí)戰(zhàn)里面的描述:
在你的代碼中始終如一地使用Optional,能非常清晰地界定出變量值的缺失是結(jié)構(gòu)上的問題李滴,還是你算法上的缺陷螃宙,抑或是你數(shù)據(jù)中的問題。另外所坯,我們還想特別強(qiáng)調(diào)谆扎,引入Optional類的意圖并非要消除每一個(gè)null引用。與此相反芹助,它的目標(biāo)是幫助你更好地設(shè)計(jì)出普適的API燕酷,讓程序員看到方法簽名籍凝,就能了解它是否接受一個(gè)Optional的值。這種強(qiáng)制會(huì)讓你更積極地將變量從Optional中解包出來苗缩,直面缺失的變量值。
所以声诸,optional的作用是顯示的表達(dá)變量的狀態(tài)酱讶,而不是為了完全消滅NPE。
二彼乌、是否使用Optional對比
2.1 不使用Optional
/**
* 常規(guī)實(shí)現(xiàn):根據(jù)客戶信息獲取車輛保險(xiǎn)公司的名稱
*
* @param person
* @return
*/
public static String getCarInsuranceName(Person person) {
//對客戶判空
if (person == null) {
return "Unknown";
}
//對車輛判空
Car car = person.getCar();
if (car == null) {
return "Unknown";
}
//對保險(xiǎn)公司判空
Insurance insurance = car.getInsurance();
if (insurance == null) {
return "Unknown";
}
return insurance.getName();
}
以上代碼泻肯,可能是我日常開發(fā)經(jīng)常遇到的,過多的NullPointerException檢查慰照,影響到了代碼的可讀性以及優(yōu)雅性灶挟,而且存在潛在的風(fēng)險(xiǎn),假設(shè)其中某個(gè)環(huán)節(jié)未做空指針的檢查毒租,則會(huì)看到我們熟悉而又討厭的NullPointerException稚铣。
2.2 使用Optional
/**
* Optional實(shí)現(xiàn):根據(jù)客戶信息獲取車輛保險(xiǎn)公司的名稱
*
* @param person
* @return
*/
public static String getCarInsuranceName2(Person person) {
//當(dāng)Optional對象為空時(shí),則后面的map都不會(huì)繼續(xù)執(zhí)行
return Optional.ofNullable(person)
.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}
2.3 測試
public static void main(String[] args) {
//測試1
Person person = new Person();
System.out.println(getCarInsuranceName(person));
System.out.println(getCarInsuranceName2(person));
System.out.println("--------------------------");
//測試2
Insurance insurance = new Insurance("xx保險(xiǎn)公司");
Car car = new Car(insurance);
Person person2 = new Person(car);
System.out.println(getCarInsuranceName(person2));
System.out.println(getCarInsuranceName2(person2));
}
運(yùn)行結(jié)果
Unknown
Unknown
--------------------------
xx保險(xiǎn)公司
xx保險(xiǎn)公司
三墅垮、Optional的構(gòu)造方式
為了控制生成實(shí)例的方式惕医,也是為了收緊空值Optional的定義,Optional將構(gòu)造函數(shù)定義為private算色。想要?jiǎng)?chuàng)建Optional實(shí)例抬伺,可以借助of和ofNullable兩個(gè)方法實(shí)現(xiàn)。這兩個(gè)方法的區(qū)別在于:of方法傳入的參數(shù)不能是null的灾梦,否則會(huì)拋出NullPointerException峡钓。所以,對于可能是null的結(jié)果若河,一定使用ofNullable能岩。
Optional類中還有一個(gè)靜態(tài)方法:empty,這個(gè)方法直接返回了內(nèi)部定義的一個(gè)常量Optional<?> EMPTY = new Optional<>()
牡肉,這個(gè)常量的value是null捧灰。ofNullable方法也是借助了empty實(shí)現(xiàn)null的包裝。所以說统锤,對于null的Optional包裝類毛俏,指向的都是相同的實(shí)例對象,Optional.empty() == Optional.ofNullable(null)
返回的是true饲窿。換句話說煌寇,空Optional是單例的。
3.1 Optional.of(T)
該方式的入?yún)⒉荒転閚ull逾雄,否則會(huì)有NPE阀溶,在確定入?yún)⒉粸榭諘r(shí)使用該方式腻脏。
3.2 Optional.ofNullable(T)
該方式的入?yún)⒖梢詾閚ull,當(dāng)入?yún)⒉淮_定為非null時(shí)使用银锻。
3.3 Optional.empty()
這種方式是返回一個(gè)空Optional永品,等效Optional.ofNullable(null)
四、常用函數(shù)
4.1 of
為非null的值創(chuàng)建一個(gè)Optional击纬。of方法通過工廠方法創(chuàng)建Optional類鼎姐。需要注意的是,創(chuàng)建對象時(shí)傳入的參數(shù)不能為null更振。如果傳入?yún)?shù)為null炕桨,則拋出NullPointerException。因此不經(jīng)常用肯腕。
Optional<Person> personOpt = Optional.of(person);
4.2 ofNullable
為指定的值創(chuàng)建一個(gè)Optional献宫,如果指定的值為null,則返回一個(gè)空的Optional实撒。
Optional<Person> personOpt = Optional.ofNullable(person);
4.3 isPresent
如果值存在返回true姊途,否則返回false。
public static String getGender(Student student){
Optional<Student> stuOpt = Optional.ofNullable(student);
if(stuOpt.isPresent())
{
return stuOpt.get().getGender();
}
return "Unkown";
}
這種用法不但沒有減少null的防御性檢查奈惑,而且增加了Optional包裝的過程吭净,違背了Optional設(shè)計(jì)的初衷,因此開發(fā)中要避免這種糟糕的使用肴甸。
4.4 ifPresent
如果Optional實(shí)例有值則為其調(diào)用consumer寂殉,否則不做處理。
public static void printName(Student student){
Optional.ofNullable(student).ifPresent(u -> System.out.println("The student name is : " + u.getName()));
}
4.5 get
如果Optional有值則將其返回原在,否則拋出NoSuchElementException友扰。因此也不經(jīng)常用。
4.6 orElse
如果有值則將其返回庶柿,否則返回指定的其它值村怪。
public static String getName(Person person) {
return Optional.ofNullable(person).map(Person::getName).orElse("Unknown");
}
4.7 orElseGet
orElseGet與orElse方法類似,區(qū)別在于得到的默認(rèn)值浮庐。orElse方法傳入的參數(shù)是明確的默認(rèn)值甚负,orElseGet方法傳入的參數(shù)是獲取默認(rèn)值的函數(shù)。
如果默認(rèn)值的構(gòu)造過程比較復(fù)雜审残,需要經(jīng)過一系列的運(yùn)算邏輯梭域,那一定要使用orElseGet,因?yàn)閛rElseGet是在值為空的時(shí)候搅轿,才會(huì)執(zhí)行函數(shù)病涨,并返回默認(rèn)值,如果值不為空璧坟,則不會(huì)執(zhí)行函數(shù)既穆,相比于orElse而言赎懦,減少了一次構(gòu)造默認(rèn)值的過程。
Optional<Wallet> findByAccountId(Integer accountId);
//為null時(shí)幻工,創(chuàng)建一下新的
Wallet wallet = repository.findByAccountId(accountId).orElseGet(() -> {
Wallet newWallet = new Wallet();
Account account = new Account();
account.setId(accountId);
newWallet.setMoney(0D);
newWallet.setAccount(account);
repository.save(newWallet);
return newWallet;
});
4.8 orElseThrow
如果有值則將其返回励两,否則拋出supplier接口創(chuàng)建的異常。
Account account = Optional.ofNullable(databaseUserRepo.findByUsername(username)).orElseThrow(() -> new UsernameNotFoundException("用戶" + username + "不存在"))
4.9 filter
如果有值并且滿足斷言條件返回包含該值的Optional会钝,否則返回空Optional伐蒋。
public static void filterAge(Student student){
Optional.ofNullable(student).filter( u -> u.getAge() > 18).ifPresent(u -> System.out.println("The student age is more than 18."));
}
4.10 map
如果有值,則對其執(zhí)行調(diào)用mapping函數(shù)得到返回值迁酸。如果返回值不為null,則創(chuàng)建包含mapping返回值的Optional作為map方法返回值俭正,否則返回空Optional奸鬓。
map()方法的源碼:
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
map()方法的參數(shù)為Function(函數(shù)式接口)對象,map()方法將Optional中的包裝對象用Function函數(shù)進(jìn)行運(yùn)算掸读,并包裝成新的Optional對象(包裝對象的類型可能改變)串远。舉例如下:
public static String getCarInsuranceName2(Person person) {
//當(dāng)Optional對象為空時(shí),則后面的map都不會(huì)繼續(xù)執(zhí)行
return Optional.ofNullable(person)
.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}
4.11 flatMap
如果有值儿惫,為其執(zhí)行mapping函數(shù)返回Optional類型返回值澡罚,否則返回空Optional。
flatMap()方法的源碼:
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
跟map()方法不同的是肾请,入?yún)unction函數(shù)的返回值類型為Optional<U>類型留搔,而不是U類型,這樣flatMap()能將一個(gè)二維的Optional對象映射成一個(gè)一維的對象铛铁,faltMap()改寫如下:
public static String getCarInsuranceName3(Person person) {
//當(dāng)Optional對象為空時(shí)隔显,則后面的flatMap都不會(huì)繼續(xù)執(zhí)行
return Optional.ofNullable(person)
.flatMap(e -> Optional.ofNullable(e.getCar()))
.flatMap(c -> Optional.ofNullable(c.getInsurance()))
.map(Insurance::getName)
.orElse("Unknown");
}
map和flatMap是對Optional的值進(jìn)行操作的方法,區(qū)別在于饵逐,map會(huì)將結(jié)果包裝到Optional中返回括眠,flatMap不會(huì)。但是兩個(gè)方法返回值都是Optional類型倍权,這也就要求掷豺,flatMap的方法函數(shù)返回值需要是Optional類型。
4.12 其他:equals薄声、hashCode当船、toString
Optional重寫了這三個(gè)方法。因?yàn)镺ptional可以認(rèn)為是包裝類奸柬,所以還是圍繞這被包裝的值重寫這三個(gè)方法生年。
equals方法,
Optional.of(s1).equals(Optional.of(s2))
完全等價(jià)于s1.equals(s2)廓奕。hashCode方法抱婉,直接返回的是值的hashCode档叔,如果是空Optional,返回的是0蒸绩。
toString方法衙四,為了能夠識別是Optional,將打印數(shù)據(jù)包裝了一下患亿。如果是空Optional传蹈,返回的是字符串“Optional.empty”;如果是非空步藕,返回是是“Optional[值的toString]”惦界。
五、正確使用Optional
5.1 盡量避免使用的地方
1咙冗、避免使用Optional.isPresent()
來檢查實(shí)例是否存在沾歪,因?yàn)檫@種方式和null != obj
沒有區(qū)別,這樣用就沒什么意義了雾消。
2灾搏、避免使用Optional.get()
方式來獲取實(shí)例對象,因?yàn)槭褂们靶枰褂肙ptional.isPresent()來檢查實(shí)例是否存在立润,否則會(huì)出現(xiàn)NPE問題狂窑。
3、避免使用Optional作為類或者實(shí)例的屬性桑腮,而應(yīng)該在返回值中用來包裝返回實(shí)例對象泉哈。
4、避免使用Optional作為方法的參數(shù)到旦,原因同3旨巷。
5.2 示例
使用Optional,我們就可以把下面這樣的代碼進(jìn)行改寫
public static String getName(Person person) {
if (person == null) {
return "Unknown";
}
return person.getName();
}
不過添忘,千萬不要改寫成這副樣子
public static String getName(Person person) {
Optional<Person> personOpt = Optional.ofNullable(person);
if (!personOpt.isPresent()) {
return "Unknown";
}
return person.getName();
}
這樣改寫非但不簡潔采呐,而且其操作還是和第一段代碼一樣。無非就是用isPresent方法來替代u==null搁骑。這樣的改寫并不是Optional正確的用法斧吐,我們再來改寫一次。
public static String getName(Person person) {
return Optional.ofNullable(person).map(Person::getName).orElse("Unknown");
}
這樣才是正確使用Optional的姿勢仲器。那么按照這種思路煤率,我們可以安心的進(jìn)行鏈?zhǔn)秸{(diào)用,而不是一層層判斷了乏冀。