大家好,我是Evan辙芍。
本文主要內(nèi)容如下:
一啡彬、前置基礎(chǔ)
Optional類源碼大量使用到:
1.四大函數(shù)式接口
2.lambda表達式
二、什么是Optional
1.Java 8新增了一個類 - Optional
2.Optional是一個容器沸手,用于放置可能為空的值外遇,它可以合理而優(yōu)雅的處理 null。
3.Optional的本質(zhì)契吉,就是內(nèi)部儲存了一個真實的值,在構(gòu)造的時候诡渴,就直接判斷其值是否為空
4.java.util.Optional<T>類本質(zhì)上就是一個容器捐晶,該容器的數(shù)值可以是空代表一個值不存在菲语,也可以是非空代表一個值存在。
5.Optional類(java.util.Optional) 是一個容器類惑灵,代表一個值存在或不存在山上,原來用 null 表示一個值不存在,現(xiàn)在 Optional 可以更好的表達這個概念英支。并且可以避免空指針異常佩憾。
2.1理論拓展
??Monad 是一種用于處理副作用的編程模式,簡單來說就是將一些可能產(chǎn)生副作用的操作封裝起來干花,并在特定的作用域內(nèi)執(zhí)行妄帘,控制其對程序產(chǎn)生的影響。在函數(shù)式編程中經(jīng)常使用 Monad 模式來處理一些副作用池凄,如 IO 操作抡驼、異常處理、狀態(tài)管理等肿仑。
Optional 是 Java 中一個非常典型的 Monad 實現(xiàn)致盟,它的主要作用是避免空指針異常并對可能為空的對象進行封裝,并提供一系列函數(shù)式的操作尤慰,如 map()
馏锡、filter()
、flatMap()
等方法伟端,使代碼更加健壯杯道、優(yōu)雅和安全。就像我們平時經(jīng)常對空值進行判空處理一樣荔泳,Optional 提供了一種更加優(yōu)美和方便的方式蕉饼,避免了深層次的嵌套判空,同時增加了代碼的可讀性和可維護性玛歌。
??對于函數(shù)式編程和 Monad 模式來說昧港,這種方式是非常重要的,因為隨著程序的規(guī)模增大支子,副作用也會越來越多创肥,這時候避免副作用對程序的影響就變得尤為重要。通過使用 Monad 模式和類似 Optional 這樣的容器類型值朋,我們可以更好地控制副作用叹侄,使程序更加穩(wěn)定和可靠。
三昨登、為什么要用Optional
1.要是用來解決程序中常見的 NullPointerException異常問題趾代。但是在實際開發(fā)過程中很多人都是在一知半解的使用 Optional,類似 if (userOpt.isPresent()){...}這樣的代碼隨處可見丰辣。如果是這樣我更愿意看到老老實實的 null 判斷撒强,這樣強行使用 Optional反而增加了代碼的復(fù)雜度禽捆。
2.這是一個明確的警示,用于提示開發(fā)人員此處要注意null值飘哨。
3.不顯式的判空胚想,當出現(xiàn)俄羅斯式套娃判空時,代碼處理上更加優(yōu)雅芽隆。
4.使用 Optional 有時候可以很方便的過濾一些屬性浊服,而且它的方法可以通過鏈式調(diào)用,方法間相互組合使用胚吁,使我們用少量的代碼就能完成復(fù)雜的邏輯牙躺。
5.防止空指針(NPE)、簡化if...else...判斷囤采、減少代碼圈復(fù)雜度
6.Optional 之所以可以解決 NPE 的問題述呐,是因為它明確的告訴我們,不需要對它進行判空蕉毯。它就好像十字路口的路標乓搬,明確地告訴你該往哪走
7.很久很久以前,為了避免 NPE代虾,我們會寫很多類似if (obj != null) {}的代碼进肯,有時候忘記寫,就可能出現(xiàn) NPE棉磨,造成線上故障江掩。在 Java 技術(shù)棧中,如果誰的代碼出現(xiàn)了 NPE乘瓤,有極大的可能會被笑話环形,這個異常被很多人認為是低級錯誤。Optional的出現(xiàn)衙傀,可以讓大家更加輕松的避免因為低級錯誤被嘲諷的概率抬吟。
8.第一是改變我們傳統(tǒng)判空的方式(其實就是幫我們包裝了一層,判空的代碼幫我們寫了)统抬,用函數(shù)式編程和申明式編程來進行對基本數(shù)據(jù)的校驗和處理火本。第二就是聲明式的編程方式對閱讀代碼的人更友好。
3.1俄羅斯式套娃判空詳解
??手動進行 if(obj!=null)的判空自然是最全能的聪建,也是最可靠的钙畔,但是怕就怕俄羅斯套娃式的 if判空。
舉例一種情況:
為了獲冉痿铩:省(Province)→市(Ctiy)→區(qū)(District)→街道(Street)→道路名(Name)
作為一個“嚴謹且良心”的后端開發(fā)工程師擎析,如果手動地進行空指針保護,我們難免會這樣寫:
public String getStreetName( Province province ) {
if( province != null ) {
City city = province.getCity();
if( city != null ) {
District district = city.getDistrict();
if( district != null ) {
Street street = district.getStreet();
if( street != null ) {
return street.getName();
}
}
}
}
return "未找到該道路名";
}
為了獲取到鏈條最終端的目的值挥下,直接鏈式取值必定有問題叔锐,因為中間只要某一個環(huán)節(jié)的對象為 null挪鹏,則代碼一定會炸见秽,并且拋出 NullPointerException異常愉烙,然而俄羅斯套娃式的 if判空實在有點心累。
Optional接口本質(zhì)是個容器解取,你可以將你可能為 null的變量交由它進行托管步责,這樣我們就不用顯式對原變量進行 null值檢測,防止出現(xiàn)各種空指針異常禀苦。
Optional語法專治上面的俄羅斯套娃式 if 判空蔓肯,因此上面的代碼可以重構(gòu)如下:
public String getStreetName( Province province ) {
return Optional.ofNullable( province )
.map( i -> i.getCity() )
.map( i -> i.getDistrict() )
.map( i -> i.getStreet() )
.map( i -> i.getName() )
.orElse( "未找到該道路名" );
}
漂亮!嵌套的 if/else判空灰飛煙滅振乏!
解釋一下執(zhí)行過程:
ofNullable(province ) :它以一種智能包裝的方式來構(gòu)造一個 Optional實例蔗包, province是否為 null均可以。如果為 null慧邮,返回一個單例空 Optional對象调限;如果非 null,則返回一個 Optional包裝對象
map(xxx ):該函數(shù)主要做值的轉(zhuǎn)換误澳,如果上一步的值非 null耻矮,則調(diào)用括號里的具體方法進行值的轉(zhuǎn)化;反之則直接返回上一步中的單例 Optional包裝對象
orElse(xxx ):很好理解忆谓,在上面某一個步驟的值轉(zhuǎn)換終止時進行調(diào)用裆装,給出一個最終的默認值
四、Optional基本知識
Optional類常用方法:
Optional.of(T t) : 創(chuàng)建一個 Optional 實例倡缠。
Optional.empty() : 創(chuàng)建一個空的 Optional 實例哨免。
Optional.ofNullable(T t):若 t 不為 null,創(chuàng)建 Optional 實例,否則創(chuàng)建空實例。
isPresent() : 判斷是否包含值昙沦。
orElse(T t) : 如果調(diào)用對象包含值琢唾,返回該值,否則返回t桅滋。
orElseGet(Supplier s) :如果調(diào)用對象包含值慧耍,返回該值,否則返回 s 獲取的值丐谋。
map(Function f): 如果有值對其處理芍碧,并返回處理后的Optional,否則返回 Optional.empty()号俐。
flatMap(Function mapper):與 map 類似泌豆,要求返回值必須是Optional。
4.1API的思考
1.of(T value)
一個東西存在那么自然有存在的價值吏饿。當我們在運行過程中踪危,不想隱藏NullPointerException蔬浙。
而是要立即報告,這種情況下就用Of函數(shù)贞远。但是不得不承認畴博,這樣的場景真的很少。我也僅在寫junit測試用例中用到過此函數(shù)蓝仲。
2.get()
直觀從語義上來看俱病,get() 方法才是最正宗的獲取 Optional 對象值的方法,
但很遺憾袱结,該方法是有缺陷的亮隙,因為假如 Optional 對象的值為 null,該方法會拋出 NoSuchElementException 異常垢夹。這完全與我們使用 Optional 類的初衷相悖溢吻。
五、工作中如何正確使用Optional
5.1 orElseThrow
orElseThrow()方法當遇到一個不存在的值的時候果元,并不返回一個默認值促王,而是拋出異常。
public void validateRequest(String requestId) {
Optional.ofNullable(requestId)
.orElseThrow(() -> new IllegalArgumentException("請求編號不能為空"));
// 執(zhí)行后續(xù)操作
}
Optional<User> optionalUser = Optional.ofNullable(null);
User user = optionalUser.orElseThrow(() -> new RuntimeException("用戶不存在"));
// 傳入 null 參數(shù)噪漾,獲取一個 Optional 對象硼砰,并使用 orElseThrow 方法
try {
Optional optional2 = Optional.ofNullable(null);
Object object2 = optional2.orElseThrow(() -> {
System.out.println("執(zhí)行邏輯,然后拋出異常");
return new RuntimeException("拋出異常");
}
);
System.out.println("輸出的值為:" + object2);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
5.2 filter
接收一個函數(shù)式接口欣硼,當符合接口時题翰,則返回一個Optional對象,否則返回一個空的Optional對象诈胜。
例如豹障,我們需要過濾出年齡在25歲到35歲之前的人群,那在Java8之前我們需要創(chuàng)建一個如下的方法來檢測每個人的年齡范圍是否在25歲到35歲之前焦匈。
public boolean filterPerson(Peron person){
boolean isInRange = false;
if(person != null && person.getAge() >= 25 && person.getAge() <= 35){
isInRange = true;
}
return isInRange;
}
public boolean filterPersonByOptional(Peron person){
return Optional.ofNullable(person)
.map(Peron::getAge)
.filter(p -> p >= 25)
.filter(p -> p <= 35)
.isPresent();
}
使用Optional看上去就清爽多了血公,這里,map()僅僅是將一個值轉(zhuǎn)換為另一個值缓熟,并且這個操作并不會改變原來的值累魔。
public class OptionalMapFilterDemo {
public static void main(String[] args) {
String password = "password";
Optional<String> opt = Optional.ofNullable(password);
Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;
Predicate<String> eq = pwd -> pwd.equals("password");
boolean result = opt.map(String::toLowerCase).filter(len6.and(len10 ).and(eq)).isPresent();
System.out.println(result);
}
}
5.3 orElse和orElseGet
結(jié)論:當optional.isPresent() == false時,orElse()和orElseGet()沒有區(qū)別够滑;
而當optional.isPresent() == true時垦写,無論你是否需要,orElse始終會調(diào)用后續(xù)函數(shù)彰触。
若方法不是純計算型的梯投,使用Optional的orElse(T);
若有與數(shù)據(jù)庫交互或者遠程調(diào)用的,都應(yīng)該使用orElseGet(Supplier)分蓖。
推薦使用orElseGet 尔艇,當存在一些復(fù)合操作,遠程調(diào)用么鹤,磁盤io等大開銷的動作禁止使用orElse终娃。
原因:當value不為空時,orElse仍然會執(zhí)行午磁。
public class GetValueDemo {
public static String getDefaultName() {
System.out.println("Getting Default Name");
return "binghe";
}
public static void main(String[] args) {
/* String text = null;
System.out.println("Using orElseGet:");
String defaultText = Optional.ofNullable(text).orElseGet(GetValueDemo::getDefaultName);
assertEquals("binghe", defaultText);
System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(GetValueDemo.getDefaultName());
assertEquals("binghe", defaultText);*/
// TODO: 2023/5/13 重點示例
String name = "binghe001";
System.out.println("Using orElseGet:");
String defaultName = Optional.ofNullable(name).orElseGet(GetValueDemo::getDefaultName);
assertEquals("binghe001", defaultName);
System.out.println("Using orElse:");
defaultName = Optional.ofNullable(name).orElse(getDefaultName());
assertEquals("binghe001", defaultName);
}
}
運行結(jié)果如下所示尝抖。
Using orElseGet:
Using orElse:
Getting default name...
可以看到,當使用orElseGet()方法時迅皇,getDefaultName()方法并不執(zhí)行,因為Optional中含有值衙熔,而使用orElse時則照常執(zhí)行登颓。所以可以看到,當值存在時红氯,orElse相比于orElseGet框咙,多創(chuàng)建了一個對象。如果創(chuàng)建對象時痢甘,存在網(wǎng)絡(luò)交互喇嘱,那系統(tǒng)資源的開銷就比較大了,這是需要我們注意的一個地方塞栅。
5.4 map和flatMap
String len = null;
Integer integer = Optional.ofNullable(len)
.map(s -> s.length())
.orElse(0);
System.out.println("integer = " + integer);
Person person = new Person("evan", 18);
Optional.ofNullable(person)
.map(p -> p.getName())
.orElse("");
Optional.ofNullable(person)
.flatMap(p -> Optional.ofNullable(p.getName()))
.orElse("");
注意:方法getName返回的是一個Optional對象者铜,如果使用map,我們還需要再調(diào)用一次get()方法放椰,而使用flatMap()就不需要了作烟。
5.5 項目實戰(zhàn)
實戰(zhàn)一
public class OptionalExample {
/**
* 測試的 main 方法
*/
public static void main(String[] args) {
// 創(chuàng)建一個測試的用戶集合
List<User> userList = new ArrayList<>();
// 創(chuàng)建幾個測試用戶
User user1 = new User("abc");
User user2 = new User("efg");
User user3 = null;
// 將用戶加入集合
userList.add(user1);
userList.add(user2);
userList.add(user3);
// 創(chuàng)建用于存儲姓名的集合
List<String> nameList = new ArrayList();
List<User> nameList03 = new ArrayList();
List<String> nameList04 = new ArrayList();
// 循環(huán)用戶列表獲取用戶信息,值獲取不為空且用戶以 a 開頭的姓名砾医,
// 如果不符合條件就設(shè)置默認值拿撩,最后將符合條件的用戶姓名加入姓名集合
/*
for (User user : userList) {
nameList.add(Optional.ofNullable(user).map(User::getName).filter(value -> value.startsWith("a")).orElse("未填寫"));
}
*/
// 輸出名字集合中的值
/* System.out.println("通過 Optional 過濾的集合輸出:");
System.out.println("nameList.size() = " + nameList.size());
nameList.stream().forEach(System.out::println);*/
/* Optional.ofNullable(userList)
.ifPresent(u -> {
for (User user : u) {
nameList04.add(Optional.ofNullable(user).map(User::getName).filter(f -> f.startsWith("e")).orElse("無名"));
}
});*/
Optional.ofNullable(userList)
.ifPresent(u -> {
u.forEach(m->{
Optional<String> stringOptional = Optional.ofNullable(m).map(User::getName).filter(f -> f.startsWith("a"));
stringOptional.ifPresent(nameList04::add);
});
});
System.out.println("nameList04.size() = " + nameList04.size());
nameList04.forEach(System.err::println);
Optional.ofNullable(userList).ifPresent(nameList03::addAll);
System.out.println("nameList03.size() = " + nameList03.size());
nameList03.stream().forEach(System.err::println);
}
}
實戰(zhàn)二
以前寫法
public String getCity(User user) throws Exception{
if(user!=null){
if(user.getAddress()!=null){
Address address = user.getAddress();
if(address.getCity()!=null){
return address.getCity();
}
}
}
throw new Excpetion("取值錯誤");
}
public String getCity(User user) throws Exception{
return Optional.ofNullable(user)
.map(u-> u.getAddress())
.map(a->a.getCity())
.orElseThrow(()->new Exception("取指錯誤"));
}
實戰(zhàn)三 簡化if.else
以前寫法
public User getUser(User user) throws Exception{
if(user!=null){
String name = user.getName();
if("zhangsan".equals(name)){
return user;
}
}else{
user = new User();
user.setName("zhangsan");
return user;
}
}
java8寫法
public User getUser(User user) {
return Optional.ofNullable(user)
.filter(u->"zhangsan".equals(u.getName()))
.orElseGet(()-> {
User user1 = new User();
user1.setName("zhangsan");
return user1;
});
}
實戰(zhàn)四 解決checkStyle問題
BaseMasterSlaveServersConfig smssc = new BaseMasterSlaveServersConfig();
if (clientName != null) {
smssc.setClientName(clientName);
}
if (idleConnectionTimeout != null) {
smssc.setIdleConnectionTimeout(idleConnectionTimeout);
}
if (connectTimeout != null) {
smssc.setConnectTimeout(connectTimeout);
}
if (timeout != null) {
smssc.setTimeout(timeout);
}
if (retryAttempts != null) {
smssc.setRetryAttempts(retryAttempts);
}
if (retryInterval != null) {
smssc.setRetryInterval(retryInterval);
}
if (reconnectionTimeout != null) {
smssc.setReconnectionTimeout(reconnectionTimeout);
}
if (password != null) {
smssc.setPassword(password);
}
if (failedAttempts != null) {
smssc.setFailedAttempts(failedAttempts);
}
// ...后面還有很多這種判斷,一個if就是一個分支如蚜,會增長圈復(fù)雜度
改造后:
Optional.ofNullable(clientName).ifPresent(smssc::setClientName);
Optional.ofNullable(idleConnectionTimeout).ifPresent(smssc::setIdleConnectionTimeout);
Optional.ofNullable(connectTimeout).ifPresent(smssc::setConnectTimeout);
Optional.ofNullable(timeout).ifPresent(smssc::setTimeout);
Optional.ofNullable(retryAttempts).ifPresent(smssc::setRetryAttempts);
Optional.ofNullable(retryInterval).ifPresent(smssc::setRetryInterval);
Optional.ofNullable(reconnectionTimeout).ifPresent(smssc::setReconnectionTimeout);
// ...縮減為一行压恒,不但減少了圈復(fù)雜度,而且減少了行數(shù)
實戰(zhàn)五 Optional提升代碼的可讀性
傳統(tǒng)操作:
public class ReadExample {
// 舉個栗子:你拿到了用戶提交的新密碼错邦,你要判斷用戶的新密碼是否符合設(shè)置密碼的規(guī)則探赫,比如長度要超過八位數(shù),然后你要對用戶的密碼進行加密兴猩。
private static String newPSWD = "12345679";
public static void main(String[] args) throws Exception {
// 簡單的清理
newPSWD = ObjectUtil.isEmpty(newPSWD) ? "" : newPSWD.trim();
// 是否符合密碼策略
if (newPSWD.length() <= 8) throw new Exception("Password rules are not met: \n" + newPSWD);
// 加密
//將 MD5 值轉(zhuǎn)換為 16 進制字符串
try {
final MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(newPSWD.getBytes(StandardCharsets.UTF_8));
newPSWD = new BigInteger(1, md5.digest()).toString(16);
} catch (
NoSuchAlgorithmException e) {
System.out.println("Encryption failed");
}
System.out.println("We saved a new password for the user: \n" + newPSWD);
}
}
優(yōu)化版本:
優(yōu)化一:
public class BetterReadExample {
// 舉個栗子:你拿到了用戶提交的新密碼期吓,你要判斷用戶的新密碼是否符合設(shè)置密碼的規(guī)則,比如長度要超過八位數(shù),然后你要對用戶的密碼進行加密讨勤。
private static String newPSWD = "888888888";
public static void main(String[] args) throws Exception {
Function<String, String> md = (o) -> {
try {
final MessageDigest md5;
md5 = MessageDigest.getInstance("MD5");
md5.update(o.getBytes(StandardCharsets.UTF_8));
return new BigInteger(1, md5.digest()).toString(16);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Encryption failed");
}
};
String digestpwd;
digestpwd = Optional.ofNullable(newPSWD)
.map(String::trim)
.filter(f -> f.length() > 8)
.map(md)
.orElseThrow(() -> new RuntimeException("Incorrect saving new password"));
System.err.println("digestpwd = " + digestpwd);
}
}
優(yōu)化二:
/**
*增加可讀性
*/
public class BetterReadExample02 {
// 舉個栗子:你拿到了用戶提交的新密碼箭跳,你要判斷用戶的新密碼是否符合設(shè)置密碼的規(guī)則,比如長度要超過八位數(shù)潭千,然后你要對用戶的密碼進行加密谱姓。
private static String newPSWD = "888888888";
//清除
private static String clean(String s){
return s.trim();
}
private static boolean filterPw(String s){
return s.length()>8;
}
private static RuntimeException myREx() {
return new RuntimeException("Incorrect saving new password");
}
public static void main(String[] args) throws Exception {
//項目實戰(zhàn)中,把main方法里面的代碼再抽出一個獨立方法
Function<String, String> md = (o) -> {
try {
final MessageDigest md5;
md5 = MessageDigest.getInstance("MD5");
md5.update(o.getBytes(StandardCharsets.UTF_8));
return new BigInteger(1, md5.digest()).toString(16);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Encryption failed");
}
};
String digestpwd;
digestpwd = Optional.ofNullable(newPSWD)
.map(BetterReadExample02::clean)
.filter(BetterReadExample02::filterPw)
.map(md)
.orElseThrow(BetterReadExample02::myREx);
System.err.println("digestpwd = " + digestpwd);
}
}
實戰(zhàn)六 大膽重構(gòu)代碼
//1. map 示例
if ( hero != null){
return "hero : " + hero.getName() + " is fire...";
} else {
return "angela";
}
//重構(gòu)成
String heroName = hero
.map(this::printHeroName)
.orElseGet(this::getDefaultName);
public void printHeroName(Hero dog){
return "hero : " + hero.getName() + " is fire...";
}
public void getDefaultName(){
return "angela";
}
//2. filter示例
Hero hero = fetchHero();
if(hero != null && hero.hasBlueBuff()){
hero.fire();
}
//重構(gòu)成
Optional<Hero> optionalHero = fetchHero();
optionalHero
.filter(Hero::hasBlueBuff)
.ifPresent(this::fire);
實戰(zhàn)七 舍棄三目運算
//第一種判空
if (Objects.notNull(taskNode.getFinishTime())) {
taskInfoVo.set(taskNode.getFinishTime().getTime());
}
//第二種判空 保留builder模式
TaskInfoVo
.builder()
.finishTime(taskNode.getFinishTime() == null ? null : taskNode.getFinishTime().getTime())
.build()));
//第三種判空
public Result<TaskInfoVo> getTaskInfo(String taskId){
TaskNode taskNode = taskExecutor.getByTaskId(String taskId);
//返回任務(wù)視圖
TaskInfoVo taskInfoVo = TaskInfoVo
.builder()
.taskName(taskNode.getName())
.finishTime(Optional.ofNullable(taskNode.getFinishTime()).map(date ->date.getTime()).orElse(null))
.user(taskNode.getUser())
.memo(taskNode.getMemo())
.build()));;
return Result.ok(taskInfoVo);
}
六刨晴、Optional操作總結(jié)
NPE 之所以討厭屉来,就是只要出現(xiàn) NPE,我們就能夠解決狈癞。但是一旦出現(xiàn)茄靠,都已經(jīng)是事后,可能已經(jīng)出現(xiàn)線上故障蝶桶。偏偏在 Java 語言中慨绳,NPE 又很容易出現(xiàn)。Optional提供了模板方法真竖,有效且高效的避免 NPE脐雪。
接下來,我們針對上面的使用恢共,總結(jié)一下:
Optional是一個包裝類战秋,且不可變,不可序列化
沒有公共構(gòu)造函數(shù)讨韭,創(chuàng)建需要使用of脂信、ofNullable方法
空Optional是單例,都是引用Optional.EMPTY
想要獲取Optional的值拐袜,可以使用get吉嚣、orElse、orElseGet蹬铺、orElseThrow
另外尝哆,還有一些實踐上的建議:
使用get方法前,必須使用isPresent檢查甜攀。但是使用isPresent前秋泄,先思考下是否可以使用orElse、orElseGet等方法代替實現(xiàn)规阀。
orElse和orElseGet恒序,優(yōu)先選擇orElseGet,這個是惰性計算
Optional不要作為參數(shù)或者類屬性谁撼,可以作為返回值
盡量將map歧胁、filter的函數(shù)參數(shù)抽出去作為單獨方法,這樣能夠保持鏈式調(diào)用
不要將null賦給Optional 雖然Optional支持null值,但是不要顯示的把null 傳遞給Optional
盡量避免使用Optional.get()
當結(jié)果不確定是否為null時喊巍,且需要對結(jié)果做下一步處理屠缭,使用Optional;
在類崭参、集合中盡量不要使用Optional 作為基本元素呵曹;
盡量不要在方法參數(shù)中傳遞Optional;
不要使用 Optional 作為Java Bean Setter方法的參數(shù)
因為Optional 是不可序列化的何暮,而且降低了可讀性奄喂。
不要使用Optional作為Java Bean實例域的類型
原因同上。
七海洼、Optional錯誤使用
1.使用在 POJO 中
public class User {
private int age;
private String name;
private Optional<String> address;
}
這樣的寫法將會給序列化帶來麻煩跨新,Optional本身并沒有實現(xiàn)序列化,現(xiàn)有的 JSON 序列化框架也沒有對此提供支持的贰军。
2.使用在注入的屬性中
這種寫法估計用的人會更少玻蝌,但不排除有腦洞的。
public class CommonService {
private Optional<UserService> userService;
public User getUser(String name) {
return userService.ifPresent(u -> u.findByName(name));
}
}
首先依賴注入大多在 spring 的框架之下词疼,直接使用 @Autowired很方便。但如果使用以上的寫法帘腹,如果 userService set 失敗了贰盗,程序就應(yīng)該終止并報異常,并不是無聲無息阳欲,讓其看起來什么問題都沒有舵盈。
- 直接使用 isPresent() 進行 if 檢查
這個直接參考上面的例子,用 if判斷和 1.8 之前的寫法并沒有什么區(qū)別球化,反而返回值包了一層 Optional秽晚,增加了代碼的復(fù)雜性,沒有帶來任何實質(zhì)的收益筒愚。其實 isPresent()一般用于流處理的結(jié)尾赴蝇,用于判斷是否符合條件。
list.stream()
.filer(x -> Objects.equals(x,param))
.findFirst()
.isPresent()
- 在方法參數(shù)中使用 Optional
我們用一個東西之前得想明白巢掺,這東西是為解決什么問題而誕生的句伶。Optional直白一點說就是為了表達可空性,如果方法參數(shù)可以為空陆淀,為何不重載呢败晴?包括使用構(gòu)造函數(shù)也一樣拆檬。重載的業(yè)務(wù)表達更加清晰直觀。
//don't write method like this
public void getUser(long uid,Optional<Type> userType);
//use Overload
public void getUser(long uid) {
getUser(uid,null);
}
public void getUser(long uid,UserType userType) {
//doing something
}
5.直接使用 Optional.get
Optional不會幫你做任何的空判斷或者異常處理,如果直接在代碼中使用 Optional.get()和不做任何空判斷一樣或油,十分危險身笤。這種可能會出現(xiàn)在那種所謂的著急上線,著急交付,對 Optional也不是很熟悉衅胀,直接就用了。這里多說一句吏恭,可能有人會反問了:甲方/業(yè)務(wù)著急拗小,需求又多,哪有時間給他去做優(yōu)化坝:摺哀九?因為我在現(xiàn)實工作中遇到過,但這兩者并不矛盾搅幅,因為代碼行數(shù)上差別并不大阅束,只要自己平時保持學習,都是信手拈來的東西茄唐。
如對您有幫助息裸,歡迎點贊,嘿嘿 !!!