Java 8 Optional使用

一 概覽

 Optional是java.util包中的一部分凤薛,因此為了使用Optional坑夯,需要:

import java.util.Optional;

二 創(chuàng)建

2.1 調(diào)用empty API, 創(chuàng)建一個(gè)空的Optional對象:

@Test
public void whenCreatesEmptyOptional_thenCorrect() {
Optional<String> empty = Optional.empty();
assertFalse(empty.isPresent());
}
Ps: isPresent API 是用來檢查Optional對象中是否有值源织。只有當(dāng)我們創(chuàng)建了一個(gè)含有非空值的Optional時(shí)才返回true。在下一部分我們將介紹這個(gè)API。

2.2 使用staticAPI創(chuàng)建

復(fù)制代碼
@Test
public void givenNonNull_whenCreatesOptional_thenCorrect() {
String name = "baeldung";
Optional<String> opt = Optional.of(name);
assertEquals("Optional[baeldung]", opt.toString());
}
復(fù)制代碼
然而着倾,傳遞給of()的值不可以為空,否則會拋出空指針異常燕少,如下:

@Test(expected = NullPointerException.class)
public void givenNull_whenThrowsErrorOnCreate_thenCorrect() {
String name = null;
Optional<String> opt = Optional.of(name);
}
有時(shí)我們需要傳遞一些空值卡者,那我們可以使用下面這個(gè)API:

復(fù)制代碼
@Test
public void givenNonNull_whenCreatesNullable_thenCorrect() {
String name = "baeldung";
Optional<String> opt = Optional.ofNullable(name);
assertEquals("Optional[baeldung]", opt.toString());
}
復(fù)制代碼
使用ofNullable API,則當(dāng)傳遞進(jìn)去一個(gè)空值時(shí)客们,不會拋出異常崇决,而只是返回一個(gè)空的Optional對象材诽,如同我們用Optional.empty API:

復(fù)制代碼
@Test
public void givenNull_whenCreatesNullable_thenCorrect() {
String name = null;
Optional<String> opt = Optional.ofNullable(name);
assertEquals("Optional.empty", opt.toString());
}
復(fù)制代碼

三 使用isPresent API 檢查值

我們可以使用這個(gè)API檢查一個(gè)Optional對象中是否有值,只有值非空才返回true恒傻。

復(fù)制代碼
@Test
public void givenOptional_whenIsPresentWorks_thenCorrect() {
Optional<String> opt = Optional.of("Baeldung");
assertTrue(opt.isPresent());

opt = Optional.ofNullable(null);
assertFalse(opt.isPresent());

}
復(fù)制代碼

四 適當(dāng)情況下使用isPresent API

傳統(tǒng)上脸侥,我們一般這樣寫來檢查空值:

if(name != null){
System.out.println(name.length);
}
問題在于,有時(shí)候我們會忘記了對空值進(jìn)行檢查盈厘,這時(shí)就可以使用這個(gè)API:

復(fù)制代碼
@Test
public void givenOptional_whenIfPresentWorks_thenCorrect() {
Optional<String> opt = Optional.of("baeldung");

opt.ifPresent(name -> System.out.println(name.length()));

}
復(fù)制代碼

五 orEse && orElseGet

5.1 orElse

這個(gè)API被用來檢索Optional對象中的值睁枕,它被傳入一個(gè)“默認(rèn)參數(shù)‘。如果對象中存在一個(gè)值沸手,則返回它外遇,否則返回傳入的“默認(rèn)參數(shù)”,如下所示:

復(fù)制代碼
@Test
public void whenOrElseWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("john");
assertEquals("john", name);
}
復(fù)制代碼

5.2 orElseGet

與orElsel類似契吉,但是這個(gè)函數(shù)不接收一個(gè)“默認(rèn)參數(shù)”跳仿,而是一個(gè)函數(shù)接口,如下例所示:

復(fù)制代碼
@Test
public void whenOrElseGetWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(() -> "john");
assertEquals("john", name);
}
復(fù)制代碼

5.3 兩者區(qū)別

要想理解這二者捐晶,首先讓我們創(chuàng)建一個(gè)無參且返回定值的方法:

public String getMyDefault() {
System.out.println("Getting Default Value");
return "Default Value";
}
接下來菲语,進(jìn)行兩個(gè)測試看看它們有什么區(qū)別:

復(fù)制代碼
@Test
public void whenOrElseGetAndOrElseOverlap_thenCorrect() {
String text;

System.out.println("Using orElseGet:");
String defaultText = 
  Optional.ofNullable(text).orElseGet(this::getMyDefault);
assertEquals("Default Value", defaultText);

System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(getMyDefault());
assertEquals("Default Value", defaultText);

}
復(fù)制代碼
在這里示例中,我們的Optional對象中包含的都是一個(gè)空值惑灵,讓我們看看程序執(zhí)行結(jié)果:

Using orElseGet:
Getting default value...
Using orElse:
Getting default value...
兩個(gè)Optional對象中都不存在value山上,因此執(zhí)行結(jié)果相同。

那么當(dāng)Optional對象中值存在時(shí)又是怎樣呢泣棋?

復(fù)制代碼
@Test
public void whenOrElseGetAndOrElseDiffer_thenCorrect() {
String text = "Text present";

System.out.println("Using orElseGet:");
String defaultText 
  = Optional.ofNullable(text).orElseGet(this::getMyDefault);
assertEquals("Text present", defaultText);

System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(getMyDefault());
assertEquals("Text present", defaultText);

}
復(fù)制代碼
讓我們看看執(zhí)行結(jié)果:

Using orElseGet:
Using orElse:
Getting default value...
可以看到胶哲,當(dāng)使用orElseGet去檢索值時(shí),getMyDefault并不執(zhí)行潭辈,因?yàn)镺ptional中含有值鸯屿,而使用orElse時(shí)則照常執(zhí)行。所以可以看到把敢,當(dāng)值存在時(shí)寄摆,orElse相比于orElseGet,多創(chuàng)建了一個(gè)對象修赞,可能從這個(gè)實(shí)例中你感受不到影響有多大婶恼,但考慮當(dāng)getDefalut不僅僅是個(gè)簡單函數(shù),而是一個(gè)web service之類的柏副,則多創(chuàng)建一個(gè)代價(jià)是比較大的勾邦。

六 orElseThrow

orElseThrow當(dāng)遇到一個(gè)不存在的值的時(shí)候,并不返回一個(gè)默認(rèn)值割择,而是拋出異常眷篇,如下所示:

復(fù)制代碼
@Test(expected = IllegalArgumentException.class)
public void whenOrElseThrowWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow(
IllegalArgumentException::new);
}
復(fù)制代碼

七 使用get()

復(fù)制代碼
@Test
public void givenOptional_whenGetsValue_thenCorrect() {
Optional<String> opt = Optional.of("baeldung");
String name = opt.get();

assertEquals("baeldung", name);

}
復(fù)制代碼
使用get() API 也可以返回被包裹著的值。但是必須是值存在時(shí)荔泳,當(dāng)值不存在時(shí)蕉饼,它會拋出一個(gè)NoSuchElementException異常虐杯,如下所示:

@Test(expected = NoSuchElementException.class)
public void givenOptionalWithNull_whenGetThrowsException_thenCorrect() {
Optional<String> opt = Optional.ofNullable(null);
String name = opt.get();
}
因?yàn)檫@個(gè)方法與我們使用Optional的目的相違背,所以可以預(yù)見在不久將來它或許會被拋棄昧港,建議還是使用其他的方法擎椰。

八 filter()

接收一個(gè)函數(shù)式接口,當(dāng)符合接口時(shí)创肥,則返回一個(gè)Optional對象达舒,否則返回一個(gè)空的Optional對象。

復(fù)制代碼
@Test
public void whenOptionalFilterWorks_thenCorrect() {
Integer year = 2016;
Optional<Integer> yearOptional = Optional.of(year);
boolean is2016 = yearOptional.filter(y -> y == 2016).isPresent();
assertTrue(is2016);
boolean is2017 = yearOptional.filter(y -> y == 2017).isPresent();
assertFalse(is2017);
}
復(fù)制代碼
這個(gè)API作用一般就是拒絕掉不符合條件的瓤的,比如拒絕掉錯(cuò)誤的電子郵箱休弃。

讓我們看下一個(gè)更有意義的例子吞歼,假如我們想買一個(gè)調(diào)制解調(diào)器圈膏,并且只關(guān)心它的價(jià)格:

復(fù)制代碼
public class Modem {
private Double price;

public Modem(Double price) {
    this.price = price;
}
//standard getters and setters

}
復(fù)制代碼
接下來,我們想要檢查每一類調(diào)制解調(diào)器是否在我們可以承受的價(jià)格范圍內(nèi)篙骡,那我們在不使用Optional時(shí)該如何做呢稽坤?

復(fù)制代碼
public boolean priceIsInRange1(Modem modem) {
boolean isInRange = false;

if (modem != null && modem.getPrice() != null
  && (modem.getPrice() >= 10
    && modem.getPrice() <= 15)) {

    isInRange = true;
}
return isInRange;

}
復(fù)制代碼
我們竟然要寫這么多code,尤其是if條件語句糯俗,然而對于這部分code最關(guān)鍵的其實(shí)是對價(jià)格范圍的判斷尿褪。

這是一個(gè)Test例子:

復(fù)制代碼
@Test
public void whenFiltersWithoutOptional_thenCorrect() {
assertTrue(priceIsInRange1(new Modem(10.0)));
assertFalse(priceIsInRange1(new Modem(9.9)));
assertFalse(priceIsInRange1(new Modem(null)));
assertFalse(priceIsInRange1(new Modem(15.5)));
assertFalse(priceIsInRange1(null));
}
復(fù)制代碼
如果長時(shí)間不用,那么有可能會忘記對null進(jìn)行檢查得湘,那么如果使用Optional还棱,會怎么樣呢立润?

復(fù)制代碼
public boolean priceIsInRange2(Modem modem2) {
return Optional.ofNullable(modem2)
.map(Modem::getPrice)
.filter(p -> p >= 10)
.filter(p -> p <= 15)
.isPresent();
}
復(fù)制代碼
map()僅僅是將一個(gè)值轉(zhuǎn)換為另一個(gè)值,請謹(jǐn)記在心,這個(gè)操作并不會改變原來的值童叠。

讓我們仔細(xì)看看這段代碼,首先雷滚,當(dāng)我們傳入一個(gè)null時(shí)代态,不會發(fā)生任何問題。其次惩淳,我們在這段code所寫的唯一邏輯就如同此方法名所描述蕉毯。

之前的那段code為了其固有的脆弱性,必須做更多思犁,而現(xiàn)在不用了代虾。

九 map()

在之前的例子中,我們使用filter()來接受/拒絕一個(gè)一個(gè)值激蹲,而使用map()我們可以將一個(gè)值轉(zhuǎn)換為另一個(gè)值棉磨。

復(fù)制代碼
@Test
public void givenOptional_whenMapWorks_thenCorrect() {
List<String> companyNames = Arrays.asList(
"paypal", "oracle", "", "microsoft", "", "apple");
Optional<List<String>> listOptional = Optional.of(companyNames);

int size = listOptional
  .map(List::size)
  .orElse(0);
assertEquals(6, size);

}
復(fù)制代碼
在這個(gè)例子中,我們使用一個(gè)List包含了一些字符串托呕,然后再把這個(gè)List包裹起來含蓉,對其map()频敛,我們這里是對這個(gè)List求它的size。

map()返回的結(jié)果也被包裹在一個(gè)Optional對象中馅扣,我們必須調(diào)用合適的方法來查看其中的值斟赚。

注意到filter()僅僅是對值進(jìn)行一個(gè)檢查并返回一個(gè)boolean(很奇怪,照前面所述不應(yīng)返回一個(gè)Optional對象嗎差油?)拗军,而map()是使用現(xiàn)有的值進(jìn)行計(jì)算,并且返回一個(gè)包裹著計(jì)算結(jié)果(映射結(jié)果)的Optional對象蓄喇。

復(fù)制代碼
@Test
public void givenOptional_whenMapWorks_thenCorrect2() {
String name = "baeldung";
Optional<String> nameOptional = Optional.of(name);

int len = nameOptional
 .map(String::length())
 .orElse(0);
assertEquals(8, len);

}
復(fù)制代碼
將filter()與map()一起使用可以做一些很強(qiáng)力的事情发侵。

假設(shè)我們現(xiàn)在要檢查一個(gè)用戶的密碼,那么我們可以這樣做:

復(fù)制代碼
@Test
public void givenOptional_whenMapWorksWithFilter_thenCorrect() {
String password = " password ";
Optional<String> passOpt = Optional.of(password);
boolean correctPassword = passOpt.filter(
pass -> pass.equals("password")).isPresent();
assertFalse(correctPassword);

correctPassword = passOpt
  .map(String::trim)
  .filter(pass -> pass.equals("password"))
  .isPresent();
assertTrue(correctPassword);

}
復(fù)制代碼
注意到妆偏,如果不進(jìn)行trim刃鳄,則會返回false,這里我們可以使用map()進(jìn)行trim钱骂。

十 flatmap()

有時(shí)我們可以使用flatmap()替換map()叔锐,二者不同之處在于,map()只有當(dāng)值不被包裹時(shí)才進(jìn)行轉(zhuǎn)換见秽,而flatmap()接受一個(gè)被包裹著的值并且在轉(zhuǎn)換之前對其解包愉烙。

我們現(xiàn)在有一個(gè)Person類:

復(fù)制代碼
public class Person {
private String name;
private int age;
private String password;

public Optional<String> getName() {
    return Optional.ofNullable(name);
}

public Optional<Integer> getAge() {
    return Optional.ofNullable(age);
}

public Optional<String> getPassword() {
    return Optional.ofNullable(password);
}
// normal constructors and setters

}
復(fù)制代碼
我們可以像對待String一樣將其包裹起來:

Person person = new Person("john", 26);
Optional<Person> personOptional = Optional.of(person);
注意當(dāng)我們包裹一個(gè)Person對象時(shí),它將包含一個(gè)嵌套的Optional例子:

復(fù)制代碼
@Test
public void givenOptional_whenFlatMapWorks_thenCorrect2() {
Person person = new Person("john", 26);
Optional<Person> personOptional = Optional.of(person);

Optional<Optional<String>> nameOptionalWrapper  
  = personOptional.map(Person::getName);
Optional<String> nameOptional  
  = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
String name1 = nameOptional.orElse("");
assertEquals("john", name1);

String name = personOptional
  .flatMap(Person::getName)
  .orElse("");
assertEquals("john", name);

}
復(fù)制代碼
需要注意解取,方法getName返回的是一個(gè)Optional對象步责,而不是像trim那樣。這樣就生成了一個(gè)嵌套的Optional對象禀苦。

因此使用map蔓肯,我們還需要再解包一次,而使用flatMap()就不需要了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伦忠,一起剝皮案震驚了整個(gè)濱河市省核,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昆码,老刑警劉巖气忠,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赋咽,居然都是意外死亡旧噪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門脓匿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淘钟,“玉大人,你說我怎么就攤上這事陪毡∶啄福” “怎么了勾扭?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铁瞒。 經(jīng)常有香客問我妙色,道長,這世上最難降的妖魔是什么慧耍? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任身辨,我火速辦了婚禮,結(jié)果婚禮上芍碧,老公的妹妹穿的比我還像新娘煌珊。我一直安慰自己,他們只是感情好泌豆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布定庵。 她就那樣靜靜地躺著,像睡著了一般践美。 火紅的嫁衣襯著肌膚如雪洗贰。 梳的紋絲不亂的頭發(fā)上找岖,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天陨倡,我揣著相機(jī)與錄音,去河邊找鬼许布。 笑死兴革,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜜唾。 我是一名探鬼主播杂曲,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼袁余!你這毒婦竟也來了擎勘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤颖榜,失蹤者是張志新(化名)和其女友劉穎棚饵,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掩完,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡噪漾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了且蓬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欣硼。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖恶阴,靈堂內(nèi)的尸體忽然破棺而出诈胜,到底是詐尸還是另有隱情豹障,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布焦匈,位于F島的核電站沼填,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏括授。R本人自食惡果不足惜坞笙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荚虚。 院中可真熱鬧薛夜,春花似錦、人聲如沸版述。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渴析。三九已至晚伙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俭茧,已是汗流浹背咆疗。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留母债,地道東北人午磁。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像毡们,于是被迫代替她去往敵國和親迅皇。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容