一 概覽
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()就不需要了