原文地址:https://www.baeldung.com/java-optional 只是將其翻譯一遍,加上些自己的理解,順便總結一下
一 概覽
Optional是java.util包中的一部分,因此為了使用Optional,需要:
import java.util.Optional;
二 創建
2.1 調用empty API, 創建一個空的Optional對象:
@Test public void whenCreatesEmptyOptional_thenCorrect() { Optional<String> empty = Optional.empty(); assertFalse(empty.isPresent()); }
Ps: isPresent API 是用來檢查Optional對象中是否有值。只有當我們創建了一個含有非空值的Optional時才返回true。在下一部分我們將介紹這個API。
2.2 使用staticAPI創建
@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
傳統上,我們一般這樣寫來檢查空值:
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對象中的值,它被傳入一個“默認參數‘。如果對象中存在一個值,則返回它,否則返回傳入的“默認參數”,如下所示:
@Test public void whenOrElseWorks_thenCorrect() { String nullName = null; String name = Optional.ofNullable(nullName).orElse("john"); assertEquals("john", name); }
5.2 orElseGet
與orElsel類似,但是這個函數不接收一個“默認參數”,而是一個函數接口,如下例所示:
@Test public void whenOrElseGetWorks_thenCorrect() { String nullName = null; String name = Optional.ofNullable(nullName).orElseGet(() -> "john"); assertEquals("john", name); }
5.3 兩者區別
要想理解這二者,首先讓我們創建一個無參且返回定值的方法:
public String getMyDefault() { System.out.println("Getting Default Value"); return "Default Value"; }
接下來,進行兩個測試看看它們有什么區別:
@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對象中包含的都是一個空值,讓我們看看程序執行結果:
Using orElseGet: Getting default value... Using orElse: Getting default value...
兩個Optional對象中都不存在value,因此執行結果相同。
那么當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); }
讓我們看看執行結果:
Using orElseGet: Using orElse: Getting default value...
可以看到,當使用orElseGet去檢索值時,getMyDefault並不執行,因為Optional中含有值,而使用orElse時則照常執行。所以可以看到,當值存在時,orElse相比於orElseGet,多創建了一個對象,可能從這個實例中你感受不到影響有多大,但考慮當getDefalut不僅僅是個簡單函數,而是一個web service之類的,則多創建一個代價是比較大的。
六 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()
接收一個函數式接口,當符合接口時,則返回一個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時,不會發生任何問題。其次,我們在這段code所寫的唯一邏輯就如同此方法名所描述。
之前的那段code為了其固有的脆弱性,必須做更多,而現在不用了。
九 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()是使用現有的值進行計算,並且返回一個包裹着計算結果(映射結果)的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()一起使用可以做一些很強力的事情。
假設我們現在要檢查一個用戶的密碼,那么我們可以這樣做:
@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()接受一個被包裹着的值並且在轉換之前對其解包。
我們現在有一個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()就不需要了。
錯誤不當之處請指出,謝謝。