學(xué)會(huì)在Java中使用Optional功能
前言
盡管存在爭(zhēng)議,但Optiont極大地改進(jìn)了Java應(yīng)用程序的設(shè)計(jì)。在本文中,我們將了解如何、何時(shí)以及在哪里最好地應(yīng)用Optional。
Optional
類的引入是Java語言設(shè)計(jì)的重大改進(jìn),但這一改進(jìn)一直存在爭(zhēng)議。在Optional
類之前,許多開發(fā)人員使用null
或異常來表示何時(shí)不存在所需的值;然而,使用Optional
類可以讓我們明確說明何時(shí)可能存在或不存在值。盡管有這種改進(jìn),但Optional
類可能會(huì)不恰當(dāng)?shù)貞?yīng)用,弊大于利。
在本文中,我們將研究Optional
類的基本原理,包括:
- 其引入背后的目的和思維過程
Optional
課程中包含的基本方法- 使用
Optional
課程的適當(dāng)時(shí)間和地點(diǎn) - 一些可以替代的技術(shù)
Java,像大多數(shù)面向?qū)ο?OO)語言一樣,對(duì)它的類型系統(tǒng)有一個(gè)秘密的后門。例如,假設(shè)我們有以下方法簽名:
public Foo doSomething();
顯然,此方法返回類型為Foo
的對(duì)象,但它也可以返回另一個(gè)值:null
。由于任何非原始類型都可以設(shè)置為null
,因此沒有什么可以阻止開發(fā)人員編寫以下方法實(shí)現(xiàn):
public Foo doSomething() { ? ?return null; }
Nullity
這為調(diào)用此方法的客戶端造成了一個(gè)繁瑣的情況。在使用從doSomething
方法返回的Foo
對(duì)象之前,客戶端必須首先檢查該對(duì)象是否為null
:
Foo foo = doSomething(); if (foo == null) { ? ? // handle null case... } else { ? ? // do something with the foo object... }
這種方法確保了Foo對(duì)象的使用不會(huì)導(dǎo)致NullPointerException
(NPE)。然而,這又產(chǎn)生了另一個(gè)問題:任何非原語對(duì)象都可以隱式地設(shè)置為null
。因此,在使用方法之前,我們必須徹底檢查方法返回的每個(gè)對(duì)象是否為空。不用說,這不僅在大型應(yīng)用程序中是不可行的,而且還會(huì)混淆代碼的清晰度。例如,為null
檢查添加額外的行會(huì)在整個(gè)應(yīng)用程序中引入樣板代碼,使Foo對(duì)象的使用變得不那么清晰(隱藏在if-else
語句中)。
根本問題是,我們不知道方法何時(shí)打算返回null
——例如當(dāng)找不到所需的值時(shí)——或者可以根據(jù)要求保證永遠(yuǎn)不會(huì)返回null
。由于我們不確定,我們被迫假設(shè)任何返回的對(duì)象都可以為null
。
一個(gè)常用的解決方案是記錄使用JavaDocs
返回值可以為null
。雖然這是對(duì)原始問題的改進(jìn),但這并不能確??蛻舳嗽谑褂脤?duì)象之前檢查空性(即Java編譯器將毫無怨無故地編譯代碼,而無需進(jìn)行這些null
檢查)。同樣,像@NotNull
注釋也存在,但這些注釋與文檔方法存在相同的缺點(diǎn)。也就是說,可以規(guī)避執(zhí)法。
Optional Class
相反,我們需要一種機(jī)制,允許方法開發(fā)人員顯式表示方法的返回值可能存在,也可能不存在。該機(jī)制以[Optional
]類的形式在Java開發(fā)工具包(JDK)7中引入。該類充當(dāng)可能不存在的對(duì)象的包裝器。因此,如果我們知道我們的doSomething
方法可能不會(huì)返回所需的Foo
對(duì)象,我們可以將簽名更改為:
public Optional<Foo> doSomething();
正如我們將在以下各節(jié)中看到的,Optional
提供了一套方法——許多功能性——允許客戶端在不存在所需值時(shí)決定該怎么做。例如,當(dāng)找不到所需的值時(shí),我們可以使用orElse
方法返回默認(rèn)值(在Optional
詞典中稱為空的Optional
)
Foo foo = doSomething().orElse(new Foo());
同樣,當(dāng)Optional
使用orElseThrow
方法為空時(shí),我們也可以拋出異常:
Foo foo = doSomething().orElseThrow(SomeException::new);
重要的是要注意兩件事:
- Java編譯器迫使我們處理空
Optional
值的情況 - 客戶負(fù)責(zé)處理缺失的期望值
雖然文檔和注釋確實(shí)將我們推向了正確、更明確的方向,但它們不允許我們將檢查缺失值的責(zé)任強(qiáng)加給客戶。另一方面,Optional
對(duì)象要求客戶端決定如何處理缺失的值。
例如,以下內(nèi)容不會(huì)編譯:
Foo foo = doSomething();
相反,我們將看到一個(gè)編譯錯(cuò)誤,提醒我們,類型為Optional<Foo>
的對(duì)象不能轉(zhuǎn)換為Foo
,因?yàn)?code>doSomething的返回類型是Optional<Foo>
,而foo
的類型是Foo
。因此,我們必須調(diào)用orElse
或orElseThrow
等方法——或get
,但我們稍后會(huì)看到為什么這不應(yīng)該是首選——以便將Optional<Foo>
對(duì)象轉(zhuǎn)換為Foo
對(duì)象。由于這兩種方法都需要一個(gè)參數(shù),因此,它們要求我們明確決定使用什么默認(rèn)值,或者如果Optional
為空,則拋出什么異常。
客戶責(zé)任
這讓我們注意到(2):處理空的Optional
的責(zé)任在于客戶。從本質(zhì)上講,doSomething
方法——本質(zhì)上返回Optional<Foo>
而不是Foo
——告訴客戶端,可能無法找到結(jié)果。因此,當(dāng)找不到結(jié)果時(shí),客戶端現(xiàn)在負(fù)責(zé)處理案件(即必須調(diào)用其中一種Optional
方法,如orElse
,才能從Optional<Foo>
到Foo
)。
這種對(duì)客戶端負(fù)責(zé)的方法意味著方法開發(fā)人員沒有足夠的信息來確定在缺少值的情況下應(yīng)該做什么。當(dāng)找不到值時(shí),開發(fā)人員可以拋出異常,但缺失的值可能不是需要例外的情況(即它可能不是例外情況)。
例如,如果我們想檢查一個(gè)對(duì)象是否已經(jīng)存在,或者如果沒有,創(chuàng)建一個(gè),那么不存在的對(duì)象不會(huì)是一個(gè)錯(cuò)誤,拋出異常是沒有道理的。此外,拋出異常需要我們?cè)谡{(diào)用代碼中捕獲異常,以創(chuàng)建默認(rèn)值。
例如,假設(shè)我們創(chuàng)建以下方法:
public Foo findIfExists() throws FooNotFoundException;
要使用現(xiàn)有值,或者如果不存在,則創(chuàng)建默認(rèn)值,我們必須執(zhí)行以下操作:
Foo foo = null; try { foo = findIfExists(); } catch (FooNotFoundException e) { foo = // create default value... }
相反,我們可以從findIfExists
返回Optional
值:
public Optional<Foo> findIfExists();
然后,我們可以簡(jiǎn)單地使用orElse
方法來提供默認(rèn)值:
Foo foo = findIfExists().orElse(/* create default value... */);
另外,后一種方法可讀性更強(qiáng)。通過簡(jiǎn)單地閱讀代碼,我們知道這兩行意味著查找是否存在或使用該值。在前一種情況下,當(dāng)findIfExists
無法找到現(xiàn)有值時(shí),我們必須有意識(shí)地將catch
子句的含義派生為默認(rèn)值。因此,Optional
詞匯比異常方法更接近預(yù)期的含義。
了解這一點(diǎn),當(dāng)客戶端負(fù)責(zé)處理丟失的對(duì)象而缺少對(duì)象不是錯(cuò)誤時(shí),Optional
是一種有用的技術(shù)。有時(shí),缺失的值可能是錯(cuò)誤——例如,當(dāng)我們假設(shè)一個(gè)值存在,而其缺失可能會(huì)給應(yīng)用程序帶來致命結(jié)果時(shí)——方法應(yīng)該拋出一個(gè)選中或未檢查的異常。然而,在某些情況下(例如findIfExists
方法),缺席對(duì)象不是錯(cuò)誤,使用Optional
是確??蛻舳孙@式處理缺失對(duì)象的有效方法。
null Optional Objects
必須解決一個(gè)警告:Optional
對(duì)象可能是null
的。由于Optional
對(duì)象和Foo
一樣是非原始對(duì)象,因此它們也可以設(shè)置為null
。例如,doSomething
的以下實(shí)現(xiàn)將編譯無錯(cuò)誤:
public Optional<Foo> doSomething() { ? ?return null;
這將導(dǎo)致客戶端必須同時(shí)處理null
返回值和處理空Optional
情況的奇怪情況:
Optional<Foo> possibleFoo = doSomething(); if (possibleFoo == null) { ? ?// handle missing object case... } else { ? ?Foo foo = possibleFoo.orElse(/* handle missing object case... */); }
這不僅為丟失的對(duì)象情況帶來了重復(fù)(即在兩個(gè)位置處理丟失的對(duì)象的情況),而且還重新引入了清晰度降低的問題。相反,當(dāng)從方法返回Optional
值時(shí),我們不應(yīng)該檢查null
值。根據(jù)Optional類文檔:
類型為Optional的變量本身永遠(yuǎn)不應(yīng)該null;它應(yīng)該始終指向Optional實(shí)例。
如果返回null
值代替Optional
對(duì)象,則方法開發(fā)人員違反了方法規(guī)定。通過聲明方法將返回Optional
對(duì)象,方法開發(fā)人員還聲明返回null
無效。由于Optional
對(duì)象表示對(duì)象丟失的可能性,因此null
值沒有有效的用例(即方法在所有情況下都應(yīng)該返回空的Optional
而不是null
)。
因此,每當(dāng)我們處理Optional
對(duì)象時(shí),我們正確地假設(shè)Optional
對(duì)象永遠(yuǎn)不會(huì)為null
。雖然Optional
對(duì)象在實(shí)踐中可能是null
的,但這個(gè)問題應(yīng)該由方法開發(fā)人員而不是客戶端解決。
重要方法
通過了解Optional
類背后的概念,我們現(xiàn)在可以看看如何在實(shí)踐中使用Optional
對(duì)象。Optional
類包含大量方法,可以分為兩類:創(chuàng)建方法和實(shí)例方法。
創(chuàng)建方法
Optional
創(chuàng)建方法是靜態(tài)方法,允許我們創(chuàng)建各種Optional
對(duì)象來滿足我們的需求。目前有三種這樣的方法:一種用于創(chuàng)建填充的Optional
(即包裝值不是null
的Optional),一種用于創(chuàng)建填充或空的Optional
,一種用于創(chuàng)建空的Optional
。
of
方法of
靜態(tài)允許我們用Optional
對(duì)象包裝現(xiàn)有對(duì)象。如果現(xiàn)有對(duì)象不是null
,則返回填充的Optional
:
Optional<Foo> foo = Optional.of(new Foo());
如果現(xiàn)有對(duì)象為null
,則拋出NPE:
Optional<Foo> foo = Optional.of(null); // 拋出 NullPointerException
ofNullable
當(dāng)傳遞非null
值時(shí), ofNullable
靜態(tài)方法與方法相同(即生成填充的Optional
),但在傳遞null
時(shí)將生成空Optional
(即不會(huì)拋出NPE):
Optional<Foo> foo1 = Optional.ofNullable(new Foo()); // populated Optional Optional<Foo> foo2 = Optional.ofNullable(null); // null Optional
當(dāng)對(duì)象的空性未知時(shí),通常使用這種方法。
empty
empty
靜態(tài)方法只需創(chuàng)建一個(gè)空的Optional
:
Optional<Foo> foo = Optional.empty(); // empty
根據(jù)定義,這種方法與以下方法相同:
Optional<Foo> foo = Optional.ofNullable(null);
正如我們將在下面幾節(jié)中看到的,empty
通常在已知沒有值存在的情況下使用。
實(shí)例方法
實(shí)例方法允許我們與現(xiàn)有的Optional
對(duì)象交互,并主要專注于查詢Optional
對(duì)象的狀態(tài),從Optional
對(duì)象獲取包裝對(duì)象,并操作Optional
對(duì)象。
isPresent&isEmpty
Optional
類中包含兩種查詢方法,允許我們檢查給定的Optional
是填充的還是空的:
isPresent
:如果填充了Optional
,則返回true
,否則返回false
isEmpty
:如果Optional
為empty
,則返回true
,否則返回false
因此,給定填充的Optional
,查詢方法將返回以下內(nèi)容:
Optional<Foo> populated = // ...populated Optional... populated.isPresent(); // true populated.isEmpty(); // false
給定一個(gè)空的Optional
,查詢方法將返回以下內(nèi)容:
Optional<Foo> empty = // ...empty Optional... populated.isPresent(); ? ?// false populated.isEmpty(); ? ? ?// true
get
如果Optional
被填充,get
方法將獲得由Optional
包裝的值,如果Optional
為空,則拋出NoSuchElementException。當(dāng)我們可以保證填充Optional時(shí),此方法可用于將現(xiàn)有Optional
轉(zhuǎn)換為其值(即從Optional<Foo>
轉(zhuǎn)換為Foo
),但我們應(yīng)該謹(jǐn)慎使用此方法。
在實(shí)踐中,保證填充Optional
需要我們首先使用isPresent
或isEmpty
方法查詢Optional
,然后調(diào)用get
:
Optional<Foo> possibleFoo = doSomething(); if (possibleFoo.isPresent()) { ? ?Foo foo = possibleFoo.get(); ? ?// ...use the foo object... } else { // ...handle case of missing Foo... }
這種模式的問題在于,這與我們?cè)谝?code>Optional之前執(zhí)行的null
檢查非常相似。因此,這種方法消除了Optional
類的固有好處。在大多數(shù)情況下,我們應(yīng)該避免使用get
方法,并使用其他方法之一(如orElse
或orElseThrow
)來獲取與填充的Optional
值相關(guān)聯(lián)。
orElse系列
orElse
系列方法允許我們獲得由Optional
包裝的值(如果填充了Optional
),或者如果Optional
為空,則獲取默認(rèn)方法。這個(gè)系列中最簡(jiǎn)單的方法是orElse
,它接受包裝類型的對(duì)象,如果Optional
是空的,則返回它。例如,給定anOptionalOptional<Foo>
對(duì)象,orElse
方法接受一個(gè)Foo
對(duì)象。如果填充了Optional
,它會(huì)返回填充值;如果Optional
為空,則返回我們傳遞給orElse
方法的Foo
對(duì)象:
Optional<Foo> possibleFoo = doSomething(); Foo foo = possibleFoo.orElse(new Foo());
然而,有時(shí)創(chuàng)建默認(rèn)值可能是一項(xiàng)昂貴的操作,并且不太可能使用。例如,默認(rèn)值可能需要建立到遠(yuǎn)程服務(wù)器的連接,或者可能需要從數(shù)據(jù)庫進(jìn)行擴(kuò)展或大型查找。如果可能填充Optional
,我們不太可能需要默認(rèn)值。使用orElse
方法,即使未使用,我們也被迫創(chuàng)建默認(rèn)值,這可能會(huì)導(dǎo)致嚴(yán)重的性能影響。
Optional
類還包括orElseGet
方法,該方法采用可以懶散創(chuàng)建默認(rèn)對(duì)象的Supplier。這允許Optional
類僅在需要時(shí)創(chuàng)建默認(rèn)對(duì)象(即僅在Optional
為空時(shí)創(chuàng)建默認(rèn)對(duì)象)。
例如:
Optional<Foo> possibleFoo = doSomething(); Foo foo = possibleFoo ? .orElseGet(() -> { /* ...lazily create a Foo object... */ });
orElseThrow系列
與orElse
方法類似,Optional
類提供了一個(gè)orElseThrow
方法,如果Optional
為空,則允許我們?cè)讷@取包裝值時(shí)拋出異常。然而,與orElse
方法不同,orElseThrow
方法有兩種形式:
- 無參數(shù)形式,如果
Optional
為空,則拋出NoSuchElementException
,如果Optional
填充,則返回包裝值 - 一個(gè)接受
Supplier
的表單,該Supplier
創(chuàng)建Throwable
對(duì)象,并在Optional
為空時(shí)拋出Throwable
對(duì)象,或在Optional
填充時(shí)返回包裝好的值
例如,我們可以從Optional<Foo>
對(duì)象中獲取Foo
對(duì)象,
如下所示:
Optional<Foo> possibleFoo = doSomething(); Foo foo = possibleFoo ? .orElseThrow();
如果Optional
是空的,將拋出NoSuchElementException
。如果填充了Optional
,則將返回包裝值。因此,orElseThrow
方法的功能與get
方法相同,但它的名稱更好地描述了其目的。因此,當(dāng)Optional
未填充時(shí),orElseThrow
方法應(yīng)使用任何值的任何地方來拋出NoSuchElementException
,而無需首先檢查它是否已填充(即不使用isPresent
或isEmpty
查詢方法)。
get
方法僅當(dāng)在Optional
查詢方法之一中使用時(shí)才應(yīng)保留用于(即首先選中Optional
的填充或空狀態(tài))。請(qǐng)注意,此orElseThrow
方法是在JDK 9中引入的,以減少圍繞get
方法使用的混亂,應(yīng)該比get方法更喜歡。
我們[在Java 8]中犯的少數(shù)錯(cuò)誤之一是命名
Optional.get()
,因?yàn)檫@個(gè)名字只是邀請(qǐng)人們?cè)诓徽{(diào)用isPresent()
用它,首先破壞了使用Optional
的全部意義......
在Java 9時(shí)間范圍內(nèi),我們建議棄用
Optional.get()
,但公眾對(duì)此的反應(yīng)是......比說冷。作為較小的一步,我們?cè)赱Java] 10中引入了orElseThrow()
...作為當(dāng)前get()
有害行為的更透明的同義詞。
Optional
類還包括一個(gè)重載的orElseThrow
方法,當(dāng)Optional
為空時(shí),該方法會(huì)拋出自定義異常。此方法接受創(chuàng)建任何Throwable
對(duì)象或Throwable
子類的對(duì)象的Suppler
并拋出它。例如:
Optional<Foo> possibleFoo = doSomething(); Foo foo = possibleFoo.orElseThrow(() -> { /* ...lazily create a Foo object... */ });
當(dāng)客戶端認(rèn)為丟失的對(duì)象是一個(gè)錯(cuò)誤,并希望在訪問空的Optional
時(shí)拋出異常時(shí),這非常有用。使用構(gòu)造函數(shù)的功能形式拋出簡(jiǎn)單的異常也是一種常見的做法:
Optional<Foo> possibleFoo = doSomething(); Foo foo = possibleFoo.orElseThrow(SomeException::new);
ifPresent系列
如果Optional被填充,ifPresent方法接受一個(gè)Consumer,該Consumer使用包裝的值執(zhí)行操作。這是使用orElse或orElseThrow方法獲得包裝對(duì)象的函數(shù)替代,主要是當(dāng)我們不希望在值不存在的情況下執(zhí)行任何操作時(shí)。
例如:
Optional<Foo> possibleFoo = doSomething(); possibleFoo.ifPresent(foo -> { /* ...do something with foo... */ });
Optional
類還包括類似的方法,ifPresentOrElse
,允許我們?cè)?code>Optional也是空時(shí)處理案例。ifPresentOrElse
方法接受的第一個(gè)參數(shù)是Consumer
,如果填充了Optional
,則使用包裝值執(zhí)行操作,而第二個(gè)參數(shù)是Runnable,如果Optional
為空,則執(zhí)行操作。因此,只有當(dāng)Optional
被填充時(shí)才會(huì)調(diào)用Consumer
,而只有當(dāng)Optional
為空時(shí),才會(huì)調(diào)用Runnable
。例如:
Optional<Foo> possibleFoo = doSomething(); possibleFoo.ifPresentOrElse( ? ?foo -> { /* ...do something with foo... */ }, ? () -> { /* ...do something when no foo found... */ }
這兩種方法的好處是,如果Optional
為空,則永遠(yuǎn)不會(huì)調(diào)用Consumer
。同樣,在ifPresentOrElse
的情況下,如果填充了Optional
,則永遠(yuǎn)不會(huì)調(diào)用Runnable
。這使我們能夠提供復(fù)雜或昂貴的操作,這些操作將根據(jù)Optional
狀態(tài)被懶惰地調(diào)用。
請(qǐng)注意,這種方法不應(yīng)該僅僅用于昂貴的操作。每當(dāng)對(duì)填充值執(zhí)行操作時(shí),都應(yīng)使用此方法。例如,如果我們只想在對(duì)象存在的情況下更新它,我們可以做一些類似于以下內(nèi)容的事情:
public class Bar { ? ?private boolean isUpdated = false; ? ?public void update() { ? ? ? ?isUpdated = true; ? } } public Optional<Bar> findBar() { ? ?// ...return a populated Bar if it could be found... } findBar().ifPresent(bar -> bar.update());
在這種情況下,如果找不到Bar
對(duì)象,我們不關(guān)心執(zhí)行任何操作。如果我們是,我們可以改用ifPresentOrElse
方法。
map
如果填充了Optional
,map
方法允許我們將包裝值從一個(gè)對(duì)象轉(zhuǎn)換為另一個(gè)對(duì)象。這種方法可以被認(rèn)為是一種管道方法,其中包裝的值沿著管道傳遞并轉(zhuǎn)換為新值。此方法的工作原理是接受應(yīng)用于包裝值的Function對(duì)象,以生成映射值。如果Optional
是空的,則永遠(yuǎn)不會(huì)調(diào)用Function
對(duì)象,并且從map
方法返回空的Optional
。
當(dāng)我們不知道是否存在一個(gè)值時(shí),這種方法非常有用,但如果存在,它應(yīng)該轉(zhuǎn)換為另一個(gè)對(duì)象。這是從數(shù)據(jù)庫讀取時(shí)常見的用例,數(shù)據(jù)庫通常存儲(chǔ)數(shù)據(jù)傳輸對(duì)象(DTO)。在大多數(shù)應(yīng)用程序中,DTO用于有效地將域?qū)ο蟠鎯?chǔ)在數(shù)據(jù)庫中,但在應(yīng)用程序的更高級(jí)別上,需要域?qū)ο蟊旧?。因此,我們必須從DTO轉(zhuǎn)換為域?qū)ο蟆?/p>
如果我們對(duì)數(shù)據(jù)庫對(duì)象進(jìn)行查找,我們可能會(huì)找到也可能找不到該對(duì)象。因此,這是返回Optional
包裝DTO的好用例。為了轉(zhuǎn)換為域?qū)ο?,我們可以使?code>map方法。例如,假設(shè)我們有一個(gè)DTO(PersonDto
),將Person
對(duì)象的名稱存儲(chǔ)在一行中,而Person
對(duì)象的名稱分為名字和姓氏(即,該名稱在PersonDto
對(duì)象中存儲(chǔ)為"John Doe"
但它在Person
對(duì)象中以"John"
的名字和"Joe"
的姓氏存儲(chǔ))。我們可以使用映射器對(duì)象從PersonDto
轉(zhuǎn)換為Person
對(duì)象,并使用映射器將從數(shù)據(jù)庫返回的PersonDto
對(duì)象映射到Person
對(duì)象:
public class Person { ? ?private String firstName; ? ?private String lastName; ? ?// ...getters & setters... } public class PersonDto { ? ?private String name; ? ?// ...getters & setters... } public class PersonMapper { ? ?public Person fromDto(PersonDto dto) { ? ? ? ?String[] names = dto.getName().split("\s+"); ? ? ? ?Person person = new Person(); ? ? ? ?person.setFirstName(names[0]); ? ? ? ?person.setLastName(names[1]); return person; ? } } public class Database { ? ?public Optional<PersonDto> findPerson() { ? ? ? ?// ...return populated DTO if DTO is found... ? } } Database db = new Database(); PersonMapper mapper = new PersonMapper(); Optional<Person> person = db.findPerson() ? .map(mapper::fromDto);
注意,可能有一個(gè)轉(zhuǎn)換會(huì)導(dǎo)致一個(gè)空的Optional
。例如,如果從給定對(duì)象到另一個(gè)對(duì)象的轉(zhuǎn)換是不可能的,那么map
方法應(yīng)該返回一個(gè)空的Optional
。執(zhí)行這種技術(shù)的反模式是讓Function
對(duì)象返回null
,然后用map
方法(使用ofNullable
,它允許我們的null
對(duì)象在不拋出異常的情況下被包裝)包裝到空的可選對(duì)象中:
Optional<Person> person = db.findPerson() ? .map(dto -> { ? ? ? ?if (dtoCanBeConverted()) { ? ? ? ? ? ?return mapper.fromDto(dto); ? ? ? } ? ? ? ?else { ? ? ? ? ? ?return null; } ? });
如果方法dtoCanBeConverted
計(jì)算為false
,則Function
對(duì)象返回null
,從而導(dǎo)致person
為空的Optional
。這種方法存在缺陷,因?yàn)樗匦乱肓?code>null值的隱式使用,其替換是Optional
類的原始目的。相反,我們應(yīng)該使用flatMap
方法,并顯式返回一個(gè)空的Optional
。
flatMap
flatMap
方法類似于map
方法,但flatMap
接受一個(gè)Function
對(duì)象,該函數(shù)將包裝值轉(zhuǎn)換為新的Optional
。與map
方法不同,flatMap
允許我們返回我們選擇的Optional
。因此,如果映射Function
無法轉(zhuǎn)換包裝值,我們可以顯式返回空的Optional
值:
Optional<Person> person = db.findPerson() ? .flatMap(dto -> { ? ? ? ?if (dtoCanBeConverted()) { ? ? ? ? ? ?Person person = return dao.fromDto(dto); ? ? ? ? ? ?return Optional.ofNullable(person); ? ? ? } ? ? ? ?else { ? ? ? ? ? ?return Optional.empty(); ? ? ? } ? });
需要注意的是,我們不再能夠像使用map
方法那樣簡(jiǎn)單地返回Person
對(duì)象。相反,我們現(xiàn)在負(fù)責(zé)將轉(zhuǎn)換后的對(duì)象包裝成Optional
。注意,如果Function
對(duì)象返回null Optional
,則拋出一個(gè)NPE
。例如,以下代碼在執(zhí)行時(shí)會(huì)拋出一個(gè)NPE
:
Optional<Person> person = db.findPerson() .flatMap(dto -> null);
filter
如果填充的Optional
滿足提供的Predicate
,則filter
方法允許我們返回填充的Optional
。因此,如果filter
方法應(yīng)用于空的Optional
,則不會(huì)調(diào)用Predicate
。同樣,如果filter
方法應(yīng)用于填充的Optional
,但包裝值不滿足提供的Predicate
(即Predicate
對(duì)象的test
方法計(jì)算false
),則返回一個(gè)空的Optional
。例如:
public class Bar { ? ?private int number; ? ?public Bar(int number) { ? ? ? ?this.number = number; ? } ? ?// ...getters & setters... } Predicate<Bar> greaterThanZero = bar -> bar.getNumber() > 0; Optional.of(new Bar(1)) ? .filter(greaterThanZero) ? .isPresent(); ? ? ? ? ? ? ?// true Optional.of(new Bar(-1)) ? .filter(greaterThanZero) .isPresent(); ? ? ? ? ? ? ?// false
何時(shí)使用
Optional
類最具爭(zhēng)議的方面之一是何時(shí)應(yīng)該和不應(yīng)該使用它。在本節(jié)中,我們將研究一些常見的用例,例如方法返回值、字段和參數(shù),其中Optional
可能非常適合也可能不合適。
返回值
正如我們?cè)诒疚闹锌吹降哪菢樱?code>Optional值非常適合方法返回值,因?yàn)檫@是其預(yù)期目的。根據(jù)Optional類文檔:
Optional
主要用于方法返回類型,其中明確需要表示“無結(jié)果”,并且使用null
可能會(huì)導(dǎo)致錯(cuò)誤。
一般來說,在以下情況下,應(yīng)使用Optional
作為返回值:
- 預(yù)計(jì)一個(gè)值可能存在,也可能不存在
- 如果缺少值,這不是錯(cuò)誤
- 客戶負(fù)責(zé)處理丟失價(jià)值的情況
Optional
返回值通常用于可能找到或找不到所需對(duì)象的查詢。
例如,存儲(chǔ)庫通常將以以下方式定義:
public interface BookRepository { ? ?public Optional<Book> findById(long id); ? ?public Optional<Book> findByTitle(String title); ? ?// ... }
這允許客戶端以適合調(diào)用方法的上下文的方式處理丟失的Book
對(duì)象,例如忽略丟失的對(duì)象、創(chuàng)建默認(rèn)對(duì)象或拋出異常。
字段
雖然Optional
對(duì)象非常適合返回類型,但它們不太適合例如字段??梢詣?chuàng)建一個(gè)類似于以下內(nèi)容的字段,但這是非常不可取的:
public class Bar { ? ?private Optional<Foo> foo; ? ?// ...getters & setters... }
Optional
應(yīng)避免字段,因?yàn)?code>Optional類不可序列化(即沒有實(shí)現(xiàn)Serializable接口)。
當(dāng)然,人們會(huì)做他們想做的事。但我們添加此功能時(shí)確實(shí)有明確的意圖,這不是一個(gè)通用的目的,也許類型,就像許多人希望我們這樣做一樣。我們的意圖是為庫方法返回類型提供有限的機(jī)制,其中需要一種明確的方法來表示“無結(jié)果”,因此使用
null
極有可能導(dǎo)致錯(cuò)誤。
因此,Optional
類型僅適用于方法返回類型。由于字段構(gòu)成類的內(nèi)部狀態(tài),外部客戶端不應(yīng)可見,如果字段被認(rèn)為是可選的,則可以創(chuàng)建一個(gè)getter,返回Optional
對(duì)象:
public class Bar { ? ?private Foo foo; ? ?public Optional<Foo> getFoo() { ? ? ? ?return Optional.ofNullable(foo); ? } }
使用這種技術(shù),客戶會(huì)明確被告知foo
值可能存在,也可能不存在,同時(shí)保持Bar
的可序列化性。
參數(shù)
在有效的情況下,方法或構(gòu)造函數(shù)的參數(shù)可能是可選的,但Optional
的不應(yīng)用于此目的。
例如,應(yīng)避免以下情況:
public class Bar { ? ?public void doSomething(Optional<Foo> foo) { ? ? ? ?// ... ? } }
不應(yīng)將參數(shù)類型設(shè)置為Optional
,而應(yīng)使用方法重載:
public class Bar { ? ?public void doSomething() { ? ? ? ?// ... ? } ? ?public void doSomething(Bar bar) { ? ? ? ?// ... ? } }
此外,具有不同方法名稱的非過載方法也可以使用:
public class Bar { ? ?public void doSomething() { ? ? ? ?// ... ? } ? ?public void doSomethingWithBar(Bar bar) { ? ? ? ?// ... ? } }
替代方案
雖然Optional
類在正確的上下文中有效,但當(dāng)可能找到或找不到所需的值時(shí),它并不是可以使用的唯一方法。在本節(jié)中,我們涵蓋了Optional
類的三種替代方案,以及如何在適當(dāng)?shù)纳舷挛闹袘?yīng)用它們。
null
最簡(jiǎn)單的替代方案是使用null
,正如我們?cè)诒疚拈_頭看到的那樣。雖然這種方法確實(shí)實(shí)現(xiàn)了我們的目標(biāo),但在引入Optional
類后,僅當(dāng)Optional
對(duì)象需要太多的開銷時(shí),才應(yīng)使用null
。此開銷可以是Optional
包裝類的額外內(nèi)存需求,也可以是執(zhí)行Optional
方法所需的額外周期。
在Optional
更有效的情況下,人們很容易以性能為借口使用null
,但是在大多數(shù)應(yīng)用程序中,Optional
類只增加了少量的開銷。除非我們處理的是低級(jí)代碼,就像來自網(wǎng)絡(luò)或驅(qū)動(dòng)程序的字節(jié)一樣,或者我們處理的是極其大量的數(shù)據(jù),否則對(duì)于方法返回類型來說,可選項(xiàng)應(yīng)該總是優(yōu)先于null
。
空對(duì)象
比null
值更有效的替代方案是引入空對(duì)象。null對(duì)象是擴(kuò)展所需類型的對(duì)象,但包含本應(yīng)為null
大小寫執(zhí)行的邏輯。例如,假設(shè)我們有以下代碼:
public class Article { ? ?private long id; ? ?public void submit() { ? ? ? ?// ... ? } ? ?// ...getters & setters... } public class ArticleRepository { ? ?public Article findById(long id) { ? ? ? ?// ...return the article if it can be found... ? } } ArticleRepository repository = new ArticleRepository(); Article article = repository.findById(1); if (article == null) { ? ?throw new ArticleNotFoundException(); } else { ? ?article.submit(); }
我們可以使用空對(duì)象重構(gòu)此代碼到以下內(nèi)容:
public class Article { ? ?// ...same as before... } public class NullArticle extends Article { ? ?@Override ? ?public void submit() { ? ? ? ?throw new ArticleNotFoundException(); ? } } public class ArticleRepository { ? ?public Article findById(long id) { ? ? ? ?if (articleIsFound()) { ? ? ? ? ? ?// return article... ? ? ? } ? ? ? ?else { ? ? ? ? ? ?return new NullArticle(); ? ? ? } ? } } ArticleRepository repository = new ArticleRepository(); Article article = repository.findById(1); article.submit();
需要注意的是,引入空對(duì)象假設(shè)方法本身知道如何處理缺失值的情況。
例外情況
我們?cè)诒疚闹锌吹降牧硪粋€(gè)替代方案是在找不到所需對(duì)象時(shí)拋出異常。如果方法知道未能找到所需的對(duì)象是一個(gè)錯(cuò)誤,則此方法有效。
例如:
public class ArticleRepository { ? ?public Article findById(long id) { ? ? ? ?if (articleIsFound()) { ? ? ? ? ? ?// return article... ? ? ? } ? ? ? ?else { ? ? ? ? ? ?throw new ArticleNotFoundException(); ? ? ? } ? } }
結(jié)論
在許多情況下,所需的值可能存在于也可能不存在于應(yīng)用程序中,以可讀和有效的方式處理這些情況是精心設(shè)計(jì)的軟件的重要組成部分。從JDK 7開始,Java包括Optional
類,該類允許開發(fā)人員返回可能存在也可能不存在的值,并允許客戶端根據(jù)發(fā)生這些情況的上下文處理這些情況。雖然Optional
類只能用于方法返回值,但了解其有用性以及如何使用簡(jiǎn)單技術(shù)應(yīng)用它是掌握現(xiàn)代Java的重要組成部分。
到此這篇關(guān)于學(xué)會(huì)在Java中使用Optional功能的文章就介紹到這了,更多相關(guān)Java使用Optional內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解析web.xml中在Servlet中獲取context-param和init-param內(nèi)的參數(shù)
本篇文章是對(duì)web.xml中在Servlet中獲取context-param和init-param內(nèi)的參數(shù)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-07-07mybatis判斷int是否為空的時(shí)候,需要注意的3點(diǎn)
這篇文章主要介紹了mybatis判斷int是否為空的時(shí)候,需要注意的3點(diǎn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07使用JWT創(chuàng)建解析令牌及RSA非對(duì)稱加密詳解
這篇文章主要介紹了JWT創(chuàng)建解析令牌及RSA非對(duì)稱加密詳解,JWT是JSON Web Token的縮寫,即JSON Web令牌,是一種自包含令牌,一種情況是webapi,類似之前的阿里云播放憑證的功能,另一種情況是多web服務(wù)器下實(shí)現(xiàn)無狀態(tài)分布式身份驗(yàn)證,需要的朋友可以參考下2023-11-11windows下使用 intellij idea 編譯 kafka 源碼環(huán)境
這篇文章主要介紹了使用 intellij idea 編譯 kafka 源碼的環(huán)境,本文是基于windows下做的項(xiàng)目演示,需要的朋友可以參考下2021-10-10Springboot登錄驗(yàn)證的統(tǒng)一攔截處理的實(shí)現(xiàn)
如果不進(jìn)行統(tǒng)一的攔截處理,每次用戶請(qǐng)求你都要去進(jìn)行用戶的信息驗(yàn)證,所以本文主要介紹了Springboot登錄驗(yàn)證的統(tǒng)一攔截處理的實(shí)現(xiàn),感興趣的可以了解一下,感興趣的可以了解一下2023-09-09