Java8之Optional用法


原文地址: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()就不需要了。

 

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM