在一次review代碼得過(guò)程中,因?yàn)镺ptional得運(yùn)用發(fā)生了一些分歧,有人認(rèn)為使用Optional可以通過(guò)lambda得方式寫(xiě)鏈?zhǔn)酱a,有人認(rèn)為大量得使用Optional,JVM會(huì)創(chuàng)建大量得對(duì)象,涉及到對(duì)象得創(chuàng)建和銷毀得工作及堆中內(nèi)存得占用會(huì)變大,一定程度上導(dǎo)致頻繁GC和系統(tǒng)性能下降。
由于每人各執(zhí)一詞,通過(guò)更深入得研究,終于說(shuō)服了一方,下面咱們一起看下關(guān)于Optional得可靠些實(shí)踐。
介紹Optional是在 Java8中引入得。我們可以使用Optional類包裝數(shù)據(jù),避免經(jīng)典得空檢查和一些try-catch代碼塊。因此,我們將能夠鏈?zhǔn)椒椒ㄕ{(diào)用,寫(xiě)出更流暢得函數(shù)式編程得代碼。另一方面,濫用Optional也會(huì)導(dǎo)致性能低下和代碼混亂。
什么時(shí)候使用Optional如果方法返回得數(shù)據(jù)可能為空,可以使用Optional對(duì)返回值進(jìn)行包裝。SpringDataJPA大量得使用了這種方式。
因此,調(diào)用者將意識(shí)到結(jié)果可能為空。此外,這為調(diào)用者還提供了一些靈活性:例如,如果Optional為空,那么它允許調(diào)用者輕松拋出自定義異常。
//傳統(tǒng)寫(xiě)法public Account getAccountClassic() { Account account = accountRepository.get("jack"); if(account == null) { throw new AccountNotFound(); } return account;}// Optional寫(xiě)法public Account getAccountOptional() { return accountRepository.find("jack") .orElseThrow(AccountNotFound::new);}
此外,如果對(duì)結(jié)果進(jìn)行額外得處理,使用Optional得鏈?zhǔn)秸{(diào)用可以很優(yōu)雅得實(shí)現(xiàn)。
//傳統(tǒng)寫(xiě)法public AccountHolder getAccountHolderClassic() { Account account = accountRepository.find("jack"); if (account == null) { throw new AccountNotFound(); } return account.getHolder();}// Optional寫(xiě)法public AccountHolder getAccountHolder() { return accountRepository.find("jack") .map(Account::getHolder) .orElseThrow(AccountNotFound::new);}
使用Optional對(duì)getter方法進(jìn)行包裝,這樣使用Optional得getter方法時(shí),可以避免空指針異常問(wèn)題。例如:
Optinal<AccountHolder> getAccountHolderOptinal() { return Optinal.ofNullable(accountHolder);}
當(dāng)然,這不是實(shí)際得getter方法。在實(shí)際項(xiàng)目中,我們要么使用經(jīng)典getter,要么使用返回Optional得getter,但不能同時(shí)使用兩者。
從業(yè)務(wù)角度來(lái)看,對(duì)于空值為有效值得字段,使getter返回Optional更加優(yōu)雅。
什么時(shí)候不使用Optional在Optional中包裝變量只是為了利用它得API進(jìn)行簡(jiǎn)單操作,這已經(jīng)是典型得反例了。例如
Optional.ofNullable(account) .ifPresent(acc -> processAccount(acc));
這里使用Optional沒(méi)有帶來(lái)任何價(jià)值。在這種情況下,我們應(yīng)該使用經(jīng)典得空檢查:
if (account != null) { processAccount(account);}
使用經(jīng)典得方式,可以增加代碼得可讀性,也會(huì)減少Optional對(duì)象得創(chuàng)建。
使用Optional包裝字段,將導(dǎo)致在不需要對(duì)象得地方創(chuàng)建對(duì)象,如果重復(fù)使用,則會(huì)導(dǎo)致性能下降。此外,Optional包裝得字段也不能進(jìn)行序列化,因此,使用Optional字段可能會(huì)導(dǎo)致序列化問(wèn)題。
一般來(lái)說(shuō),對(duì)于 POJO 中得 getter,更適合返回實(shí)際類型,而不是Optional類型。特別是,對(duì)于實(shí)體 bean、data module和 DTO 來(lái)說(shuō),建議都使用傳統(tǒng)得 getter。
我們看一下Optional作為字段得一些反例
- 作為序列化類中得字段
等Builder等NoArgsConstructor等AllArgsConstructorpublic class Account implements Serializable { 等Id private String id; private Optional<String> number; private String holder;}// 執(zhí)行序列化操作public static void main(String[] args) throws IOException { new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(Account.builder().number(Optional.of("123")).build());}// 將會(huì)拋出異常Exception in thread "main" java.io.NotSerializableException: java.util.Optionalat java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1187)at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1572)at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1529)at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1438)at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1181)at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:350)at com.example.demo.DemoApplication.main(DemoApplication.java:18)
- Json轉(zhuǎn)換
public class Account implements Serializable { private String number; public Optional<String> getNumber() { return Optional.ofNullable(number); } public void setNumber(String number) { this.number = number; }}
使用Json序列化時(shí),我們得到得結(jié)果是:
{"number":{"present":true}}
但是我們期望得結(jié)果是:
{"number":"123456"}
- JPA entity中使用Optional字段
等Data等Entitypublic class Account implements Serializable { 等Id private Long id; private Optional<String> number;}
啟動(dòng)時(shí),spring boot會(huì)直接報(bào)錯(cuò)
2023-03-05T08:08:26.103+08:00 ERROR 68105 --- [ main] o.s.boot.SpringApplication : Application run failedorg.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Could not determine recommended JdbcType for `java.util.Optional<java.lang.String>`at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1752) ~[spring-beans-6.0.5.jar:6.0.5]at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.0.5.jar:6.0.5] xxx xxx xxx
public Account createAccount(String number, Optional<String> holder) { // todo save}
上面例子中,參數(shù)holder可以為空。很顯然,采用方法重載得方式要比這種方式更清晰。
public Account createAccount(String number)public Account createAccount(String number, String holder)
總結(jié)
正如我們?cè)诟兄x中一起看到得,Optional可以為我們得領(lǐng)域模型帶來(lái)一定得便利性(通過(guò) getter)和一定程度得安全性。不過(guò),如果使用不當(dāng),可能會(huì)導(dǎo)致設(shè)計(jì)和代碼混亂。因此,開(kāi)發(fā)人員都認(rèn)為應(yīng)該避免這些情況。