It takes a strong man to save himself, and a great man to save another.
一 概覽
Optional是java.util包中的一部分咖祭,因此為了使用Optional么翰,需要: import java.util.Optional
二 創(chuàng)建
2.1 調用empty API, 創(chuàng)建一個空的Optional對象:
@Test
public void whenCreatesEmptyOptional_thenCorrect() {
Optional<String> empty = Optional.empty();
assertFalse(empty.isPresent());
}
Ps: isPresent API 是用來檢查Optional對象中是否有值檐迟。只有當我們創(chuàng)建了一個含有非空值的Optional時才返回true追迟。在下一部分我們將介紹這個API敦间。
2.2 使用staticAPI創(chuàng)建
@Test
public void givenNonNull_whenCreatesOptional_thenCorrect() {
String name = "baeldung";
Optional<String> opt = Optional.of(name); assertEquals("Optional[baeldung]", opt.toString());
}
然而,傳遞給of()的值不可以為空厢绝,否則會拋出空指針異常昔汉,如下:
@Test(expected = NullPointerException.class)
public void givenNull_whenThrowsErrorOnCreate_thenCorrect() {
String name = null;
Optional<String> opt = Optional.of(name);
}
有時我們需要傳遞一些空值浓利,那我們可以使用下面這個API:
@Test
public void givenNonNull_whenCreatesNullable_thenCorrect() {
String name = "baeldung";
Optional<String> opt = Optional.ofNullable(name); assertEquals("Optional[baeldung]", opt.toString());
}
使用ofNullable API贷掖,則當傳遞進去一個空值時渴语,不會拋出異常,而只是返回一個空的Optional對象牙甫,如同我們用Optional.empty API:
@Test
public void givenNull_whenCreatesNullable_thenCorrect() {
String name = null;
Optional<String> opt = Optional.ofNullable(name); assertEquals("Optional.empty", opt.toString());
}
三 使用isPresent API 檢查值
我們可以使用這個API檢查一個Optional對象中是否有值窟哺,只有值非空才返回true技肩。
@Test
public void givenOptional_whenIsPresentWorks_thenCorrect() {
Optional<String> opt = Optional.of("Baeldung");
assertTrue(opt.isPresent());
opt = Optional.ofNullable(null);
assertFalse(opt.isPresent());
}
四 適當情況下使用isPresent API
傳統(tǒng)上,我們一般這樣寫來檢查空值:
if(name != null){
System.out.println(name.length);
}
問題在于旋奢,有時候我們會忘記了對空值進行檢查至朗,這時就可以使用這個API:
@Test
public void givenOptional_whenIfPresentWorks_thenCorrect() {
Optional<String> opt = Optional.of("baeldung");
opt.ifPresent(name -> System.out.println(name.length()));
}
五 orEse && orElseGet
5.1 orElse
這個API被用來檢索Optional對象中的值锹引,它被傳入一個“默認參數(shù)‘粤蝎。如果對象中存在一個值袋马,則返回它,否則返回傳入的“默認參數(shù)”碑宴,如下所示:
@Test
public void whenOrElseWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("john");
assertEquals("john", name);
}
5.2 orElseGet
與orElsel類似祸挪,但是這個函數(shù)不接收一個“默認參數(shù)”贞间,而是一個函數(shù)接口,如下例所示:
@Test
public void whenOrElseGetWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(() -> "john");
assertEquals("john", name);
}
5.3 兩者區(qū)別
要想理解這二者整以,首先讓我們創(chuàng)建一個無參且返回定值的方法:
public String getMyDefault() {
System.out.println("Getting Default Value");
return "Default Value";
}
接下來公黑,進行兩個測試看看它們有什么區(qū)別:
@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);
}
在這里示例中,我們的Optional對象中包含的都是一個空值吭从,讓我們看看程序執(zhí)行結果:
Using orElseGet:
Getting default value...
Using orElse:
Getting default value...
兩個Optional對象中都不存在value,因此執(zhí)行結果相同谱醇。
那么當Optional對象中值存在時又是怎樣呢枣抱?
@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);
}
讓我們看看執(zhí)行結果:
Using orElseGet:
Using orElse:
Getting default value...
可以看到佳晶,當使用orElseGet去檢索值時讼载,getMyDefault并不執(zhí)行,因為Optional中含有值菇篡,而使用orElse時則照常執(zhí)行一喘。所以可以看到嗜暴,當值存在時议蟆,orElse相比于orElseGet咐容,多創(chuàng)建了一個對象舆逃,可能從這個實例中你感受不到影響有多大,但考慮當getDefalut不僅僅是個簡單函數(shù)戳粒,而是一個web service之類的路狮,則多創(chuàng)建一個代價是比較大的。
六 orElseThrow
orElseThrow當遇到一個不存在的值的時候蔚约,并不返回一個默認值奄妨,而是拋出異常,如下所示:
@Test(expected = IllegalArgumentException.class)
public void whenOrElseThrowWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow(
IllegalArgumentException::new);
}
七 使用get()
@Test
public void givenOptional_whenGetsValue_thenCorrect() {
Optional<String> opt = Optional.of("baeldung");
String name = opt.get();
assertEquals("baeldung", name);
}
使用get() API 也可以返回被包裹著的值炊琉。但是必須是值存在時展蒂,當值不存在時又活,它會拋出一個NoSuchElementException異常苔咪,如下所示:
@Test(expected = NoSuchElementException.class)
public void givenOptionalWithNull_whenGetThrowsException_thenCorrect() {
Optional<String> opt = Optional.ofNullable(null);
String name = opt.get();
}
因為這個方法與我們使用Optional的目的相違背团赏,所以可以預見在不久將來它或許會被拋棄,建議還是使用其他的方法体谒。
八 filter()
接收一個函數(shù)式接口,當符合接口時故响,則返回一個Optional對象誓酒,否則返回一個空的Optional對象。
@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);
}
這個API作用一般就是拒絕掉不符合條件的,比如拒絕掉錯誤的電子郵箱。
讓我們看下一個更有意義的例子,假如我們想買一個調制解調器扒磁,并且只關心它的價格:
public class Modem {
private Double price;
public Modem(Double price) {
this.price = price;
}
//standard getters and setters}
接下來,我們想要檢查每一類調制解調器是否在我們可以承受的價格范圍內兰伤,那我們在不使用Optional時該如何做呢?
public boolean priceIsInRange1(Modem modem) {
boolean isInRange = false;
if (modem != null && modem.getPrice() != null && (modem.getPrice() >= 10
&& modem.getPrice() <= 15)) {
isInRange = true;
}
return isInRange;
}
我們竟然要寫這么多code,尤其是if條件語句,然而對于這部分code最關鍵的其實是對價格范圍的判斷。
這是一個Test例子:
@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));
}
如果長時間不用沃缘,那么有可能會忘記對null進行檢查,那么如果使用Optional水慨,會怎么樣呢朝抖?
public boolean priceIsInRange2(Modem modem2) {
return Optional.ofNullable(modem2)
.map(Modem::getPrice)
.filter(p -> p >= 10)
.filter(p -> p <= 15)
.isPresent();
}
map()僅僅是將一個值轉換為另一個值砌滞,請謹記在心绊茧,這個操作并不會改變原來的值。
讓我們仔細看看這段代碼,首先况芒,當我們傳入一個null時耐版,不會發(fā)生任何問題。其次落君,我們在這段code所寫的唯一邏輯就如同此方法名所描述焙蚓。
之前的那段code為了其固有的脆弱性萌京,必須做更多,而現(xiàn)在不用了较坛。
九 map()
在之前的例子中丑勤,我們使用filter()來接受/拒絕一個一個值,而使用map()我們可以將一個值轉換為另一個值岔霸。
@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);
}
在這個例子中絮爷,我們使用一個List包含了一些字符串柜蜈,然后再把這個List包裹起來藻雪,對其map()捷绒,我們這里是對這個List求它的size字逗。
map()返回的結果也被包裹在一個Optional對象中跟狱,我們必須調用合適的方法來查看其中的值。
注意到filter()僅僅是對值進行一個檢查并返回一個boolean(很奇怪扛门,照前面所述不應返回一個Optional對象嗎?)纵寝,而map()是使用現(xiàn)有的值進行計算,并且返回一個包裹著計算結果(映射結果)的Optional對象。
@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);
}
將filter()與map()一起使用可以做一些很強力的事情。
假設我們現(xiàn)在要檢查一個用戶的密碼,那么我們可以這樣做:
@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);
}
注意到佑附,如果不進行trim秃嗜,則會返回false锅锨,這里我們可以使用map()進行trim。
十 flatmap()
有時我們可以使用flatmap()替換map()谱邪,二者不同之處在于扯俱,map()只有當值不被包裹時才進行轉換晴玖,而flatmap()接受一個被包裹著的值并且在轉換之前對其解包。
我們現(xiàn)在有一個Person類:
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}
我們可以像對待String一樣將其包裹起來:
Person person = new Person("john", 26);
Optional<Person> personOptional = Optional.of(person);
注意當我們包裹一個Person對象時锐帜,它將包含一個嵌套的Optional例子:
@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);}
需要注意药蜻,方法getName返回的是一個Optional對象,而不是像trim那樣踱卵。這樣就生成了一個嵌套的Optional對象。
因此使用map原朝,我們還需要再解包一次锥忿,而使用flatMap()就不需要了。
錯誤不當之處請指出惶楼,謝謝右蹦。