1. 程式人生 > 實用技巧 >Java8 Optional用法

Java8 Optional用法

  根據Oracle文件,Optional是一個容器物件,可以包含也可以不包含非null值。Optional在Java 8中引入,目的是解決 NullPointerExceptions的問題。本質上,Optional是一個包裝器類,其中包含對其他物件的引用。在這種情況下,物件只是指向記憶體位置的指標,並且也可以指向任何內容。從其它角度看,Optional提供一種型別級解決方案來表示可選值而不是空引用。   在Java 8之前,程式設計師將返回null而不是Optional。這種方法有一些缺點。一種是沒有明確的方法來表示null可能是一個特殊值。相比之下,在API中返回Optional是明確的宣告,其中可能沒有值。如果我們要確保不會出現空指標異常,則需要對每個引用進行顯式的空檢查,如下所示,我們都同意這是很多樣板。   
// Life before Optional
    private void getIsoCode( User user){
        if (user != null) {
            Address address = user.getAddress();
            if (address != null) {
                Country country = address.getCountry();
                if (country != null) {
                    String isocode = country.getIsocode();
                    if (isocode != null) {
                        isocode = isocode.toUpperCase();
                    }
                }
            }
        }
    }

  Optional的特性

public final class Optional<T> {
    //Null指標的封裝
    private static final java.util.Optional<?> EMPTY = new java.util.Optional<>();
    //內部包含的值物件
    private final T value;
    private Optional() ;
    //返回EMPTY物件
    public static<T> java.util.Optional<T> empty() ;
    //建構函式,但是value為null,會報NPE
    private Optional(T value);
    //靜態工廠方法,但是value為null,會報NPE
    public static <T> java.util.Optional<T> of(T value);
    //靜態工廠方法,value可以為null
    public static <T> java.util.Optional<T> ofNullable(T value) ;
    //獲取value,但是value為null,會報NoSuchElementException
    public T get() ;
    //返回value是否為null
    public boolean isPresent();
    //如果value不為null,則執行consumer式的函式,為null不做事
    public void ifPresent(Consumer<? super T> consumer) ;
     //過濾,如果value不為null,則根據條件過濾,為null不做事
    public java.util.Optional<T> filter(Predicate<? super T> predicate) ;
     //轉換,在其外面封裝Optional,如果value不為null,則map轉換,為null不做事
    public<U> java.util.Optional<U> map(Function<? super T, ? extends U> mapper);
     //轉換,如果value不為null,則map轉換,為null不做事
    public<U> java.util.Optional<U> flatMap(Function<? super T, java.util.Optional<U>> mapper) ;
    //value為null時,預設提供other值
    public T orElse(T other);
      //value為null時,預設提供other值
    public T orElseGet(Supplier<? extends T> other);
      //value為null時,預設提供other值
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) ;
}

  

Optional類提供了大約10種方法,我們可以使用它們來建立和使用Optional類,下面將介紹如何使用它們。

建立一個Optional類

這是用於建立可選例項的三種建立方法。

  1. static <T> [Optional]<T> [empty]()

返回一個空的Optional例項。

// Creating an empty optional
Optional<String> empty = Optional.empty();

在返回一個空的{Optional}例項時,Optional的值不存在。不過,這樣做可能很有誘惑力,如果物件為空,請避免與Option.empty()返回的例項的{==}比較 。因為不能保證它是一個單例,反之,應該使用isPresent()。

  1. static <T> [Optional]<T> [of](T value)

返回特定的非空值Optional。

// Creating an optional using of

String name = "java";

Optional<String> opt = Optional.of(name);

靜態方法需要一個非null引數;否則,將引發空指標異常。因此,如果我們不知道引數是否為null,那就是我們使用 ofNullable的時候,下面將對此進行介紹。

  1. static <T> [Optional]<T> [of](T value)

返回描述指定值的Optional,如果非空,則返回空值。

// Possible null value

 Optional<String> optional = Optional.ofNullable(name());

  private  String  name(){

  String name = "Java";

  return (name.length() > 5) ? name : null;

 }

如果我們傳入一個空引用,它不會丟擲異常,而是返回一個空的Optional物件:

所以這就是動態或手動建立Optional的三種方法。下一組方法用於檢查值的存在。

  1. 布林值[isPresent]()

如果存在值,則返回true;反之,返回false。如果所包含的物件不為null,則返回true,反之返回false。通常在對物件執行任何其他操作之前,先在Optional上呼叫此方法。

//ispresent

Optional<String> optional1 = Optional.of("javaone");

if (optional1.isPresent()){

//Do something, normally a get

}

布林值[isEmpty()]

如果存在值,則返回false;否則,返回ture。這與isPresent 相反, 並且僅在Java 11及更高版本中可用。

//isempty

Optional<String> optional1 = Optional.of("javaone");

if (optional1.isEmpty()){

  //Do something

}
  1. void [ifPresent]([Consumer]<? super [T]> consumer)

如果存在值,則使用該值呼叫指定的使用者;否則,什麼都不做。

如果您不熟悉Java 8,那麼您可能會想知道:什麼是消費者?簡單來說,消費者是一種接受引數且不返回任何內容的方法。當使用 ifPresent時,這個方法就是一石二鳥。我們可以執行值存在性檢查並使用一種方法執行預期的操作,如下所示。

//ifpresent

Optional<String> optional1 = Optional.of("javaone");

optional1.ifPresent(s -> System.out.println(s.length()));
可選類提供了另一組用於獲取可選值的方法。
  1. T[get]()

如果此Optional中存在值,則返回該值,否則丟擲 NoSuchElementException。在這之後,我們想要的是儲存在Optional中的值,我們可以通過get()來獲取它。但是,當該值為null時,此方法將引發異常。這就需要 orElse() 方法來緊急救援。

//get
Optional<String> optional1 = Optional.of("javaone");
if (optional1.isPresent()){ 
  String value = optional1.get();
}
  1. [T ][orElse][T]其他)

返回值(如果存在);反之,返回其他。

該 orElse() 方法用於檢索包裝在Optional例項內的值。它採用一個充當預設值的引數。該 orElse() 方法返回包裝的值(如果存在)及其引數,反之:

 //orElse
        String nullName = null;
        String name = Optional.ofNullable(nullName).orElse("default_name");

如果這還不夠,那麼Optional類將繼續提供另一種獲取值的方法,即使該方法的null稱為 orElseGet()。

  1. [T][orElseGet]([Supplier]<? extends [T]> other)

返回值(如果存在);否則,呼叫other並返回該呼叫的結果。

該orElseGet() 方法類似於 orElse()。但是,如果沒有Optional值,則不採用返回值,而是採用供應商功能介面,該介面將被呼叫並返回呼叫的值:

 //orElseGet
        String name = Optional.ofNullable(nullName).orElseGet(() -> "john");

那麼,orElse() 和orElseGet()之間有什麼區別。

乍一看,這兩種方法似乎具有相同的效果。但是,事實並非如此。讓我們建立一些示例,以突出兩者之間的相似性和行為差異。

首先,讓我們看看它們在物件為空時的行為:

String text = null;
String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultValue);
defaultText = Optional.ofNullable(text).orElse(getDefaultValue());
public String getDefaultValue() {
    System.out.println("Getting Default Value");
    return "Default Value";
}
在上面的示例中,我們在Optional物件中包裝了一個空文字,然後嘗試使用兩種方法中的每一種來獲取包裝後的值。副作用如下:
Getting default value...
Getting default value...
在每種情況下都會呼叫預設方法。碰巧的是,當不存在包裝的值時,兩者 orElse() 和的 orElseGet() 工作方式完全相同。

現在,讓我們執行另一個該值存在測試,理想情況下,甚至不應建立預設值:

在這個簡單的示例中,建立預設物件不會花費很多成本,因為JVM知道如何處理此類物件。但是,當諸如此類的方法 default 必須進行Web服務呼叫或者查詢資料庫時,則成本變得非常明顯。

1,建立 Optional 例項
重申一下,這個型別的物件可能包含值,也可能為空。你可以使用同名方法建立一個空的 Optional。

Optional<User> emptyOpt = Optional.empty();
emptyOpt.get();

毫不奇怪,嘗試訪問 emptyOpt 變數的值會導致 NoSuchElementException。

你可以使用 of() 和 ofNullable() 方法建立包含值的 Optional。兩個方法的不同之處在於如果你把 null 值作為引數傳遞進去,of() 方法會丟擲 NullPointerException:

Optional<User> opt = Optional.of(user);

因此,你應該明確物件不為 null 的時候使用 of()。

如果物件即可能是 null 也可能是非 null,你就應該使用 ofNullable() 方法:

Optional<User> opt = Optional.ofNullable(user);

2,訪問 Optional 物件的值

從 Optional 例項中取回實際值物件的方法之一是使用 get() 方法:

String name = "John";
Optional<String> opt = Optional.ofNullable(name);

assertEquals("John", opt.get());

不過,你看到了,這個方法會在值為 null的時候丟擲異常。要避免異常,你可以選擇首先驗證是否有值:

User user = new User("[email protected]", "1234");
Optional<User> opt = Optional.ofNullable(user);
assertTrue(opt.isPresent());

assertEquals(user.getEmail(), opt.get().getEmail());

檢查是否有值的另一個選擇是 ifPresent() 方法。該方法除了執行檢查,還接受一個Consumer(消費者) 引數,如果物件不是空的,就對執行傳入的 Lambda 表示式:

opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));

這個例子中,只有 user 使用者不為 null 的時候才會執行斷言。

接下來,我們來看看提供空值的方法。

3,返回預設值
Optional類提供了API用以返回物件值,或者在物件為空的時候返回預設值:orElse(),
如果有值則返回該值,否則返回傳遞給它的引數值:

User user2 = new User("[email protected]", "1234");
User result = Optional.ofNullable(user).orElse(user2);

assertEquals(user2.getEmail(), result.getEmail());

這裡 user 物件是空的,所以返回了作為預設值的 user2。

如果物件的初始值不是 null,那麼預設值會被忽略:

User user = new User("[email protected]","1234");
User user2 = new User("[email protected]", "1234");
User result = Optional.ofNullable(user).orElse(user2);

assertEquals("[email protected]", result.getEmail());

第二個同類型的 API 是 orElseGet() —— 其行為略有不同。這個方法會在有值的時候返回值,如果沒有值,它會執行作為引數傳入的 Supplier(供應者) 函式式介面,並將返回其執行結果:

User result = Optional.ofNullable(user).orElseGet( () -> user2);

4,orElse() 和 orElseGet() 的不同之處

乍一看,這兩種方法似乎起著同樣的作用。然而事實並非如此。我們建立一些示例來突出二者行為上的異同。

我們先來看看物件為空時他們的行為:

User user = null
logger.debug("Using orElse");
User result = Optional.ofNullable(user).orElse(createNewUser());
logger.debug("Using orElseGet");
User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());


private User createNewUser() {
logger.debug("Creating New User");
return new User("[email protected]", "1234");
}

上面的程式碼中,兩種方法都呼叫了 createNewUser() 方法,這個方法會記錄一個訊息並返回 User 物件。

程式碼輸出如下:

Using orElse
Creating New User
Using orElseGet
Creating New User

由此可見,當物件為空而返回預設物件時,行為並無差異。

我們接下來看一個類似的示例,但這裡 Optional 不為空:

@Test
public void givenPresentValue_whenCompare_thenOk() {
  User user = new User("[email protected]", "1234");
  logger.info("Using orElse");
  User result = Optional.ofNullable(user).orElse(createNewUser());
  logger.info("Using orElseGet");
  User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}

這次的輸出:

Using orElse
Creating New User
Using orElseGet

這個示例中,兩個 Optional 物件都包含非空值,兩個方法都會返回對應的非空值。不過,orElse() 方法仍然建立了 User 物件。與之相反,orElseGet() 方法不建立 User 物件。

在執行較密集的呼叫時,比如呼叫 Web 服務或資料查詢,這個差異會對效能產生重大影響。

5,返回異常
除了 orElse() 和 orElseGet() 方法,Optional 還定義了 orElseThrow() API —— 它會在物件為空的時候丟擲異常,而不是返回備選的值:

User result = Optional.ofNullable(user).orElseThrow( () -> new IllegalArgumentException());

這裡,如果 user 值為 null,會丟擲 IllegalArgumentException。

這個方法讓我們有更豐富的語義,可以決定丟擲什麼樣的異常,而不總是丟擲 NullPointerException。

現在我們已經很好地理解了如何使用 Optional,我們來看看其它可以對 Optional 值進行轉換和過濾的方法。

6,轉換值
有很多種方法可以轉換 Optional 的值。我們從 map() 和 flatMap() 方法開始。

先來看一個使用 map() API 的例子:

User user = new User("[email protected]", "1234");
String email = Optional.ofNullable(user)
.map(u -> u.getEmail()).orElse("[email protected]");

assertEquals(email, user.getEmail());

map() 對值應用(呼叫)作為引數的函式,然後將返回的值包裝在 Optional 中。這就使對返回值進行鏈試呼叫的操作成為可能 —— 這裡的下一環就是 orElse()。

相比這下,flatMap() 也需要函式作為引數,並對值呼叫這個函式,然後直接返回結果。

下面的操作中,我們給 User 類添加了一個方法,用來返回 Optional:

public class User { 
    private String position;

    public Optional<String> getPosition() {
        return Optional.ofNullable(position);
    }

//...
}        

既然 getter 方法返回 String 值的 Optional,你可以在對 User 的 Optional 物件呼叫 flatMap() 時,用它作為引數。其返回的值是解除包裝的 String 值:

User user = new User("[email protected]", "1234");
user.setPosition("Developer");
String position = Optional.ofNullable(user)
.flatMap(u -> u.getPosition()).orElse("default");

assertEquals(position, user.getPosition().get());

7,過濾值

除了轉換值之外,Optional 類也提供了按條件“過濾”值的方法。

filter() 接受一個 Predicate 引數,返回測試結果為 true 的值。如果測試結果為 false,會返回一個空的 Optional。

來看一個根據基本的電子郵箱驗證來決定接受或拒絕 User(使用者) 的示例:

User user = new User("[email protected]", "1234");
Optional<User> result = Optional.ofNullable(user)
.filter(u -> u.getEmail() != null && u.getEmail().contains("@"));

assertTrue(result.isPresent());

如果通過過濾器測試,result 物件會包含非空值。

8,Optional 類的鏈式方法
為了更充分的使用 Optional,你可以連結組合其大部分方法,因為它們都返回相同類似的物件。

我們使用 Optional 重寫最早介紹的示例。

首先,重構類,使其 getter 方法返回 Optional 引用:

public class User {
    private Address address;

    public Optional<Address> getAddress() {
        return Optional.ofNullable(address);
    }

    // ...
}
public class Address {
    private Country country;

    public Optional<Country> getCountry() {
        return Optional.ofNullable(country);
    }

 // ...
}

現在可以刪除 null 檢查,替換為 Optional 的方法:

@Test
public void whenChaining_thenOk() {
    User user = new User("[email protected]", "1234");

    String result = Optional.ofNullable(user)
    .flatMap(u -> u.getAddress())
    .flatMap(a -> a.getCountry())
    .map(c -> c.getIsocode())
    .orElse("default");

    assertEquals(result, "default");
}

上面的程式碼可以通過方法引用進一步縮減:

String result = Optional.ofNullable(user)
.flatMap(User::getAddress)
.flatMap(Address::getCountry)
.map(Country::getIsocode)
.orElse("default");

結果現在的程式碼看起來比之前採用條件分支的冗長程式碼簡潔多了。

四,Java 9 增強
我們介紹了 Java 8 的特性,Java 9 為 Optional 類添加了三個方法:or()、ifPresentOrElse() 和 stream()。

or() 方法與 orElse() 和 orElseGet() 類似,它們都在物件為空的時候提供了替代情況。or() 的返回值是由 Supplier 引數產生的另一個 Optional 物件。

如果物件包含值,則 Lambda 表示式不會執行:

User result = Optional.ofNullable(user)
.or( () -> Optional.of(new User("default","1234"))).get();

assertEquals(result.getEmail(), "default");

上面的示例中,如果 user 變數是 null,它會返回一個 Optional,它所包含的 User 物件,其電子郵件為 “default”。

ifPresentOrElse() 方法需要兩個引數:一個 Consumer 和一個 Runnable。如果物件包含值,會執行 Consumer 的動作,否則執行 Runnable。


如果你想在有值的時候執行某個動作,或者只是跟蹤是否定義了某個值,那麼這個方法非常有用:

Optional.ofNullable(user).ifPresentOrElse( u -> logger.info("User is:" + u.getEmail()),
() -> logger.info("User not found"));

最後介紹的是新的 stream() 方法,它通過把例項轉換為 Stream 物件,讓你從廣大的 Stream API 中受益。如果沒有值,它會得到空的 Stream;有值的情況下,Stream 則會包含單一值。

我們來看一個把 Optional 處理成 Stream 的例子:

User user = new User("[email protected]", "1234");
List<String> emails = Optional.ofNullable(user)
.stream()
.filter(u -> u.getEmail() != null && u.getEmail().contains("@"))
.map( u -> u.getEmail())
.collect(Collectors.toList());

assertTrue(emails.size() == 1);
assertEquals(emails.get(0), user.getEmail());

這裡對 Stream 的使用帶來了其 filter()、map() 和 collect() 介面,以獲取 List。

使用Optional最佳實踐

就像程式語言的任何其他功能一樣,它可以正確使用或被濫用。為了瞭解使用Optional類的最佳方法,需要了解以下內容:

1.它解決的問題

Optional的方法是嘗試通過增加構建更具表現力的API的可能性來減少Java系統中空指標異常的情況,這些API解釋了有時缺少返回值的可能性。

如果從一開始就存在Optional,那麼大多數庫和應用程式可能會更好地處理缺少的返回值,從而減少了空指標異常的數量以及總體上的錯誤總數。

2.它不解決的問題

Optional並不意味著是一種避免所有型別的空指標的機制。例如,它仍然必須測試方法和建構函式的強制輸入引數。

像使用null時一樣,Optional不能幫助傳達缺失值的含義。以類似的方式,null可能意味著很多不同的東西(找不到值等),因此缺少Optional值也可以。

該方法的呼叫方仍然需要檢查該方法的JavaDoc以理解預設選項的含義,以便正確地處理它。

同樣,以一種類似的方式,可以將檢查的異常捕獲在一個空塊中,沒有什麼阻止呼叫方進行呼叫 get() 並繼續進行。

3.何時使用

Optional的預期用途主要是作為返回型別。獲取此型別的例項後,可以提取該值(如果存在)或提供其他行為(如果不存在)。

Optional類的一個非常有用的用例是將其與流或返回Optional值以構建流暢的API的其他方法結合。請參見下面的程式碼段

User user = users.stream().findFirst().orElse(new User("default", "1234"));
4.什麼時候不使用

a)不要將其用作類中的欄位,因為它不可序列化

如果確實需要序列化包含Optional值的物件,則Jackson庫提供了將Optionals視為普通物件的支援。這意味著Jackson將空物件視為空,將具有值的物件視為包含該值的欄位。可以在jackson-modules-java8專案中找到此功能。

b)不要將其用作建構函式和方法的引數,因為這會導致不必要的複雜程式碼。

User user = new User("[email protected]", "1234", Optional.empty());

  



轉載連結:https://www.jianshu.com/p/362010f310b9