大家好,我是Evan。
本文主要內(nèi)容如下:
一、前置基礎(chǔ)
Optional類源碼大量使用到:
1.四大函數(shù)式接口
2.lambda表達(dá)式
二、什么是Optional
1.Java 8新增了一個類 - Optional
2.Optional是一個容器,用于放置可能為空的值,它可以合理而優(yōu)雅的處理 null。
3.Optional的本質(zhì),就是內(nèi)部儲存了一個真實(shí)的值,在構(gòu)造的時候,就直接判斷其值是否為空
4.java.util.Optional<T>類本質(zhì)上就是一個容器,該容器的數(shù)值可以是空代表一個值不存在,也可以是非空代表一個值存在。
5.Optional類(java.util.Optional) 是一個容器類,代表一個值存在或不存在,原來用 null 表示一個值不存在,現(xiàn)在 Optional 可以更好的表達(dá)這個概念。并且可以避免空指針異常。
2.1理論拓展
??Monad 是一種用于處理副作用的編程模式,簡單來說就是將一些可能產(chǎn)生副作用的操作封裝起來,并在特定的作用域內(nèi)執(zhí)行,控制其對程序產(chǎn)生的影響。在函數(shù)式編程中經(jīng)常使用 Monad 模式來處理一些副作用,如 IO 操作、異常處理、狀態(tài)管理等。
Optional 是 Java 中一個非常典型的 Monad 實(shí)現(xiàn),它的主要作用是避免空指針異常并對可能為空的對象進(jìn)行封裝,并提供一系列函數(shù)式的操作,如 map()
、filter()
、flatMap()
等方法,使代碼更加健壯、優(yōu)雅和安全。就像我們平時經(jīng)常對空值進(jìn)行判空處理一樣,Optional 提供了一種更加優(yōu)美和方便的方式,避免了深層次的嵌套判空,同時增加了代碼的可讀性和可維護(hù)性。
??對于函數(shù)式編程和 Monad 模式來說,這種方式是非常重要的,因?yàn)殡S著程序的規(guī)模增大,副作用也會越來越多,這時候避免副作用對程序的影響就變得尤為重要。通過使用 Monad 模式和類似 Optional 這樣的容器類型,我們可以更好地控制副作用,使程序更加穩(wěn)定和可靠。
三、為什么要用Optional
1.要是用來解決程序中常見的 NullPointerException異常問題。但是在實(shí)際開發(fā)過程中很多人都是在一知半解的使用 Optional,類似 if (userOpt.isPresent()){...}這樣的代碼隨處可見。如果是這樣我更愿意看到老老實(shí)實(shí)的 null 判斷,這樣強(qiáng)行使用 Optional反而增加了代碼的復(fù)雜度。
2.這是一個明確的警示,用于提示開發(fā)人員此處要注意null值。
3.不顯式的判空,當(dāng)出現(xiàn)俄羅斯式套娃判空時,代碼處理上更加優(yōu)雅。
4.使用 Optional 有時候可以很方便的過濾一些屬性,而且它的方法可以通過鏈?zhǔn)秸{(diào)用,方法間相互組合使用,使我們用少量的代碼就能完成復(fù)雜的邏輯。
5.防止空指針(NPE)、簡化if...else...判斷、減少代碼圈復(fù)雜度
6.Optional 之所以可以解決 NPE 的問題,是因?yàn)樗鞔_的告訴我們,不需要對它進(jìn)行判空。它就好像十字路口的路標(biāo),明確地告訴你該往哪走
7.很久很久以前,為了避免 NPE,我們會寫很多類似if (obj != null) {}的代碼,有時候忘記寫,就可能出現(xiàn) NPE,造成線上故障。在 Java 技術(shù)棧中,如果誰的代碼出現(xiàn)了 NPE,有極大的可能會被笑話,這個異常被很多人認(rèn)為是低級錯誤。Optional的出現(xiàn),可以讓大家更加輕松的避免因?yàn)榈图夊e誤被嘲諷的概率。
8.第一是改變我們傳統(tǒng)判空的方式(其實(shí)就是幫我們包裝了一層,判空的代碼幫我們寫了),用函數(shù)式編程和申明式編程來進(jìn)行對基本數(shù)據(jù)的校驗(yàn)和處理。第二就是聲明式的編程方式對閱讀代碼的人更友好。
3.1俄羅斯式套娃判空詳解
??手動進(jìn)行 if(obj!=null)的判空自然是最全能的,也是最可靠的,但是怕就怕俄羅斯套娃式的 if判空。
舉例一種情況:
為了獲?。菏?Province)→市(Ctiy)→區(qū)(District)→街道(Street)→道路名(Name)
作為一個“嚴(yán)謹(jǐn)且良心”的后端開發(fā)工程師,如果手動地進(jìn)行空指針保護(hù),我們難免會這樣寫:
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 "未找到該道路名";
}
為了獲取到鏈條最終端的目的值,直接鏈?zhǔn)饺≈当囟ㄓ袉栴},因?yàn)橹虚g只要某一個環(huán)節(jié)的對象為 null,則代碼一定會炸,并且拋出 NullPointerException異常,然而俄羅斯套娃式的 if判空實(shí)在有點(diǎn)心累。
Optional接口本質(zhì)是個容器,你可以將你可能為 null的變量交由它進(jìn)行托管,這樣我們就不用顯式對原變量進(jìn)行 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實(shí)例, province是否為 null均可以。如果為 null,返回一個單例空 Optional對象;如果非 null,則返回一個 Optional包裝對象
map(xxx ):該函數(shù)主要做值的轉(zhuǎn)換,如果上一步的值非 null,則調(diào)用括號里的具體方法進(jìn)行值的轉(zhuǎn)化;反之則直接返回上一步中的單例 Optional包裝對象
orElse(xxx ):很好理解,在上面某一個步驟的值轉(zhuǎn)換終止時進(jìn)行調(diào)用,給出一個最終的默認(rèn)值
四、Optional基本知識
Optional類常用方法:
Optional.of(T t) : 創(chuàng)建一個 Optional 實(shí)例。
Optional.empty() : 創(chuàng)建一個空的 Optional 實(shí)例。
Optional.ofNullable(T t):若 t 不為 null,創(chuàng)建 Optional 實(shí)例,否則創(chuàng)建空實(shí)例。
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)
一個東西存在那么自然有存在的價值。當(dāng)我們在運(yùn)行過程中,不想隱藏NullPointerException。
而是要立即報告,這種情況下就用Of函數(shù)。但是不得不承認(rèn),這樣的場景真的很少。我也僅在寫junit測試用例中用到過此函數(shù)。
2.get()
直觀從語義上來看,get() 方法才是最正宗的獲取 Optional 對象值的方法,
但很遺憾,該方法是有缺陷的,因?yàn)榧偃?Optional 對象的值為 null,該方法會拋出 NoSuchElementException 異常。這完全與我們使用 Optional 類的初衷相悖。
五、工作中如何正確使用Optional
5.1 orElseThrow
orElseThrow()方法當(dāng)遇到一個不存在的值的時候,并不返回一個默認(rèn)值,而是拋出異常。
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ù)式接口,當(dāng)符合接口時,則返回一個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é)論:當(dāng)optional.isPresent() == false時,orElse()和orElseGet()沒有區(qū)別;
而當(dāng)optional.isPresent() == true時,無論你是否需要,orElse始終會調(diào)用后續(xù)函數(shù)。
若方法不是純計算型的,使用Optional的orElse(T);
若有與數(shù)據(jù)庫交互或者遠(yuǎn)程調(diào)用的,都應(yīng)該使用orElseGet(Supplier)。
推薦使用orElseGet ,當(dāng)存在一些復(fù)合操作,遠(yuǎn)程調(diào)用,磁盤io等大開銷的動作禁止使用orElse。
原因:當(dāng)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 重點(diǎn)示例
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);
}
}
運(yùn)行結(jié)果如下所示。
Using orElseGet:
Using orElse:
Getting default name...
可以看到,當(dāng)使用orElseGet()方法時,getDefaultName()方法并不執(zhí)行,因?yàn)镺ptional中含有值,而使用orElse時則照常執(zhí)行。所以可以看到,當(dāng)值存在時,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 項(xiàng)目實(shí)戰(zhàn)
實(shí)戰(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è)置默認(rèn)值,最后將符合條件的用戶姓名加入姓名集合
/*
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);
}
}
實(shí)戰(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("取指錯誤"));
}
實(shí)戰(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;
});
}
實(shí)戰(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ù)
實(shí)戰(zhàn)五 Optional提升代碼的可讀性
傳統(tǒng)操作:
public class ReadExample {
// 舉個栗子:你拿到了用戶提交的新密碼,你要判斷用戶的新密碼是否符合設(shè)置密碼的規(guī)則,比如長度要超過八位數(shù),然后你要對用戶的密碼進(jìn)行加密。
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 進(jìn)制字符串
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ù),然后你要對用戶的密碼進(jìn)行加密。
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ù),然后你要對用戶的密碼進(jìn)行加密。
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 {
//項(xiàng)目實(shí)戰(zhàn)中,把main方法里面的代碼再抽出一個獨(dú)立方法
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);
}
}
實(shí)戰(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);
實(shí)戰(zhàn)七 舍棄三目運(yù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
另外,還有一些實(shí)踐上的建議:
使用get方法前,必須使用isPresent檢查。但是使用isPresent前,先思考下是否可以使用orElse、orElseGet等方法代替實(shí)現(xiàn)。
orElse和orElseGet,優(yōu)先選擇orElseGet,這個是惰性計算
Optional不要作為參數(shù)或者類屬性,可以作為返回值
盡量將map、filter的函數(shù)參數(shù)抽出去作為單獨(dú)方法,這樣能夠保持鏈?zhǔn)秸{(diào)用
不要將null賦給Optional 雖然Optional支持null值,但是不要顯示的把null 傳遞給Optional
盡量避免使用Optional.get()
當(dāng)結(jié)果不確定是否為null時,且需要對結(jié)果做下一步處理,使用Optional;
在類、集合中盡量不要使用Optional 作為基本元素;
盡量不要在方法參數(shù)中傳遞Optional;
不要使用 Optional 作為Java Bean Setter方法的參數(shù)
因?yàn)镺ptional 是不可序列化的,而且降低了可讀性。
不要使用Optional作為Java Bean實(shí)例域的類型
原因同上。
七、Optional錯誤使用
1.使用在 POJO 中
public class User {
private int age;
private String name;
private Optional<String> address;
}
這樣的寫法將會給序列化帶來麻煩,Optional本身并沒有實(shí)現(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() 進(jìn)行 if 檢查
這個直接參考上面的例子,用 if判斷和 1.8 之前的寫法并沒有什么區(qū)別,反而返回值包了一層 Optional,增加了代碼的復(fù)雜性,沒有帶來任何實(shí)質(zhì)的收益。其實(shí) isPresent()一般用于流處理的結(jié)尾,用于判斷是否符合條件。
list.stream()
.filer(x -> Objects.equals(x,param))
.findFirst()
.isPresent()
- 在方法參數(shù)中使用 Optional
我們用一個東西之前得想明白,這東西是為解決什么問題而誕生的。Optional直白一點(diǎn)說就是為了表達(dá)可空性,如果方法參數(shù)可以為空,為何不重載呢?包括使用構(gòu)造函數(shù)也一樣。重載的業(yè)務(wù)表達(dá)更加清晰直觀。
//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)化???因?yàn)槲以诂F(xiàn)實(shí)工作中遇到過,但這兩者并不矛盾,因?yàn)榇a行數(shù)上差別并不大,只要自己平時保持學(xué)習(xí),都是信手拈來的東西。
如對您有幫助,歡迎點(diǎn)贊,嘿嘿 !!!