欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

學(xué)會(huì)在Java中使用Optional功能

 更新時(shí)間:2022年09月07日 08:58:15   作者:吃人陳  
這篇文章主要介紹了學(xué)會(huì)在Java中使用Optional功能,在本文中,我們將了解如何、何時(shí)以及在哪里最好地應(yīng)用Optional,具體相關(guān)內(nèi)容需要的朋友可以參考下面文章內(nèi)容

前言

盡管存在爭(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)用orElseorElseThrow等方法——或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:如果Optionalempty,則返回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需要我們首先使用isPresentisEmpty方法查詢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方法,并使用其他方法之一(如orElseorElseThrow)來獲取與填充的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,而無需首先檢查它是否已填充(即不使用isPresentisEmpty查詢方法)。

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)文章

  • mybatis框架入門學(xué)習(xí)教程

    mybatis框架入門學(xué)習(xí)教程

    MyBatis是一個(gè)支持普通SQL查詢,存儲(chǔ)過程和高級(jí)映射的優(yōu)秀持久層框架。這篇文章主要介紹了mybatis框架入門學(xué)習(xí)教程,需要的朋友可以參考下
    2017-02-02
  • Java獲取客戶端真實(shí)IP地址過程解析

    Java獲取客戶端真實(shí)IP地址過程解析

    這篇文章主要介紹了Java獲取客戶端真實(shí)IP地址過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • SpringCloud feign無法注入接口的問題

    SpringCloud feign無法注入接口的問題

    這篇文章主要介紹了SpringCloud feign無法注入接口的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 解析web.xml中在Servlet中獲取context-param和init-param內(nèi)的參數(shù)

    解析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-07
  • Java常用JVM參數(shù)實(shí)戰(zhàn)

    Java常用JVM參數(shù)實(shí)戰(zhàn)

    本文主要介紹了Java常用JVM參數(shù)實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • mybatis判斷int是否為空的時(shí)候,需要注意的3點(diǎn)

    mybatis判斷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創(chuàng)建解析令牌及RSA非對(duì)稱加密詳解,JWT是JSON Web Token的縮寫,即JSON Web令牌,是一種自包含令牌,一種情況是webapi,類似之前的阿里云播放憑證的功能,另一種情況是多web服務(wù)器下實(shí)現(xiàn)無狀態(tài)分布式身份驗(yàn)證,需要的朋友可以參考下
    2023-11-11
  • windows下使用 intellij idea 編譯 kafka 源碼環(huán)境

    windows下使用 intellij idea 編譯 kafka 源碼環(huán)境

    這篇文章主要介紹了使用 intellij idea 編譯 kafka 源碼的環(huán)境,本文是基于windows下做的項(xiàng)目演示,需要的朋友可以參考下
    2021-10-10
  • Springboot登錄驗(yàn)證的統(tǒng)一攔截處理的實(shí)現(xiàn)

    Springboot登錄驗(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
  • 使用Jackson 處理 null 或者 空字符串

    使用Jackson 處理 null 或者 空字符串

    這篇文章主要介紹了使用Jackson 處理 null 或者 空字符串,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08

最新評(píng)論