Java8之Optional用法

原文地址:https://www.baeldung.com/java-optional  只是將其翻譯一遍,加上些本身的理解,順便總結一下java

一 概覽web

     Optional是java.util包中的一部分,所以爲了使用Optional,須要:oracle

import java.util.Optional;

 

二 建立app

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建立this

@Test
public void givenNonNull_whenCreatesOptional_thenCorrect() {
    String name = "baeldung";
    Optional<String> opt = Optional.of(name);
    assertEquals("Optional[baeldung]", opt.toString());
}

然而,傳遞給of()的值不能夠爲空,不然會拋出空指針異常,以下:spa

@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()就不須要了。

 

 

錯誤不當之處請指出,謝謝。

相關文章
相關標籤/搜索