Java空指針檢查實(shí)在看不下去了——轉(zhuǎn)用Optional真香
前言
在Java開發(fā)中,空指針是程序員遇到的最多的異常之一(特別是剛接觸java開發(fā)的),對于對象中的某個(gè)屬性,有時(shí)候我們?yōu)榱吮苊獬绦驁?bào)空指針錯(cuò)誤,而不得不使用較多的if、else來進(jìn)行邏輯判斷,但這樣的話代碼可能就會(huì)比較冗余或者說不夠優(yōu)雅。雖然我們大部分程序員是有責(zé)任心的,不會(huì)坐視不管,于是就有了大量的 null 值檢查。盡管有時(shí)候這種檢查完全沒有必要,但我們已經(jīng)習(xí)慣了例行公事。終于Java 8 看不下去了,就引入了 Optional,以便我們編寫的代碼不再那么呆板。
NPE問題
NPE問題就是我們在開發(fā)中經(jīng)常碰到的NullPointerException.假設(shè)我們有兩個(gè)類,他們的UML類圖如下圖所示:
現(xiàn)在需要訪問用戶地址信息的省份,簡單代碼為:user.getAddress().getProvince();
在這種寫法中,當(dāng)user為null時(shí),是有可能報(bào)NPE異常的。為了解決這個(gè)問題,于是采用下面的寫法:
public String OptGetProvince(User user){
if(user!=null){
Address address = user.getAddress();
if(address!=null){
String province = address.getProvince();
return province;
}
return "none";
}
}
這種寫法是比較繁瑣的,為了避免上述丑陋的寫法。于是JAVA8提供了Optional類來優(yōu)化這種寫法:
public String OptGetProvince(User user){
return Optional.ofNullable(user)
.map(s -> s.getAddress())
.map(a -> a.getProvince())
.orElse("none");
}
可以看到,通過Optional的使用,可以很好的解決if以及嵌套判空的問題,使得整體的判斷變得清爽簡潔多了。
Optional使用
我們可以把Optional類看成是一個(gè)容器,我們將對象存儲(chǔ)到容器中后,通過調(diào)用內(nèi)置的API,可以較為安全地過濾掉可能存在的空指針問題,避免繁瑣嵌套的if、else操作,讓我們的代碼盡可能的簡潔。API主要分5個(gè)大類。
構(gòu)造函數(shù): empty,of,ofNullable
empty返回一個(gè)空的Optional對象。
Optional.empty();
of根據(jù)傳入的值生成Optional對象。
// 方式2 將非空對象作為屬性傳入Optional類中
User u = new User("小明",16);
Optional.of(u.getAddress());
ofNullable 和of方法一樣,根據(jù)傳入的值生成optional對象。
// 方式3 將非空對象作為屬性傳入Optional類中
User u = new User("小明",16);
Optional.ofNullable(u.getAddress());
of和ofNullable的作用很相近,從Optional類的源代碼看的話,可以發(fā)現(xiàn)對于ofNullable方法的話是有進(jìn)行判空的。也就是說,如果使用of方法傳入的參數(shù)是null,同樣會(huì)報(bào)空指針。
值選擇方法:orElse,orElseGet和orElseThrow
這三個(gè)方法相當(dāng)于SQL中的IFNULL函數(shù),若Optional中值為null,則返回給定的默認(rèn)值。
orElse
User s = new User("小明",16,new Address());
String result = Optional.ofNullable(s.getAddress().getProvince()).orElse("深圳");
orElseGet
User s = new User("小明",16,new Address());
String result = Optional.ofNullable(s.getAddress().getProvince()).orElseGet(()->"深圳");
orElseThrow
User s = new User("小明",16,new Address());
String s3 = Optional.ofNullable(s.getAddress().getProvince()).orElseThrow(() -> new IllegalArgumentException("缺少參數(shù)"));
對于orElseThrow和orElseGet兩個(gè)方法,是采用函數(shù)式接口的方式來作為參數(shù)的。同時(shí),對于orElse和orElseGet兩個(gè)方法,作用相近,區(qū)別是若Optional對象中的值不為空,則orElseGet不會(huì)創(chuàng)建參數(shù)中的對象,而orElse無論什么情況都會(huì)創(chuàng)建參數(shù)對象。
判空函數(shù):isPresent和ifPresent
兩個(gè)函數(shù)的用法類似,都可以用作判空,區(qū)別在于當(dāng)不為空時(shí),ifPresent會(huì)執(zhí)行對應(yīng)的函數(shù)。
isPresent
User user = new User("小明",16,new Address());
boolean b1 = Optional.ofNullable(user.getAddress()).isPresent();
System.out.println(b1); // true
ifPresent
User user = new User("小明",16,new Address());
Optional.ofNullable(user.getAddress()).ifPresent(address -> System.out.println(address));
值轉(zhuǎn)換函數(shù):map和flagMap
值轉(zhuǎn)換的就是對Optional對象中的value值進(jìn)行轉(zhuǎn)換,對值應(yīng)用(調(diào)用)作為參數(shù)的函數(shù),然后將返回的值包裝在 Optional中
map
User user = new User("小明",16,new Address());
String s1 = Optional.ofNullable(user).map(s -> s.getName()).get();
flagMap
User user = new User("小明",16,new Address());
String s1 = Optional.ofNullable(user).flatMap(s -> s.getName()).get();
兩個(gè)函數(shù)都可以實(shí)現(xiàn)值的轉(zhuǎn)換,兩者的區(qū)別是二者的入?yún)⒉煌?。以上面的flagMap的示例代碼為例,我們需要在User類中重寫一下getName方法,使其返回Optional對象。
過濾(篩選)函數(shù):filter
該函數(shù)的作用是,判斷Optional中的值是否滿足指定條件,若滿足則返回,否則返回一個(gè)EMPTY對象。
User user = new User("小明",16,new Address());
User result = Optional.ofNullable(user).filter(s -> s.getName().equals("小紅")).orElseGet(() ->new User("小藍(lán)",10));
System.out.println(result); // user{address=null, name='小藍(lán)', age=10}
這里會(huì)篩選出滿足姓名為小紅的User對象,若不滿足則新建一個(gè)姓名為小藍(lán)的User對象。
最后
需要注意,使用Optonal這種鏈?zhǔn)骄幊屉m然簡潔化了程序代碼,但是邏輯性不是很明顯,相對來說會(huì)損失一定的代碼可讀性,具體的使用需要開發(fā)人員在實(shí)際場景中進(jìn)行權(quán)衡。個(gè)人建議哪怕是自己不經(jīng)常使用也要盡量掌握,避免出現(xiàn)閱讀源碼的時(shí)候顯得尷尬。