// 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類
這是用於創建可選實例的三種創建方法。
- static <T> [Optional]<T> [empty]()
返回一個空的Optional實例。
// Creating an empty optional Optional<String> empty = Optional.empty();
在返回一個空的{Optional}實例時,Optional的值不存在。不過,這樣做可能很有誘惑力,如果對象為空,請避免與Option.empty()返回的實例的{==}比較 。因為不能保證它是一個單例,反之,應該使用isPresent()。
- 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的時候,下面將對此進行介紹。
- 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的三種方法。下一組方法用於檢查值的存在。
- 布爾值[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 }
- void [ifPresent]([Consumer]<? super [T]> consumer)
如果存在值,則使用該值調用指定的使用者;否則,什么都不做。
如果您不熟悉Java 8,那么您可能會想知道:什么是消費者?簡單來說,消費者是一種接受參數且不返回任何內容的方法。當使用 ifPresent時,這個方法就是一石二鳥。我們可以執行值存在性檢查並使用一種方法執行預期的操作,如下所示。
//ifpresent Optional<String> optional1 = Optional.of("javaone"); optional1.ifPresent(s -> System.out.println(s.length()));
- T[get]()
如果此Optional中存在值,則返回該值,否則拋出 NoSuchElementException。在這之后,我們想要的是存儲在Optional中的值,我們可以通過get()來獲取它。但是,當該值為null時,此方法將引發異常。這就需要 orElse() 方法來緊急救援。
//get Optional<String> optional1 = Optional.of("javaone"); if (optional1.isPresent()){ String value = optional1.get(); }
- [T ][orElse]([T]其他)
返回值(如果存在);反之,返回其他。
該 orElse() 方法用於檢索包裝在Optional實例內的值。它采用一個充當默認值的參數。該 orElse() 方法返回包裝的值(如果存在)及其參數,反之:
//orElse String nullName = null; String name = Optional.ofNullable(nullName).orElse("default_name");
如果這還不夠,那么Optional類將繼續提供另一種獲取值的方法,即使該方法的null稱為 orElseGet()。
- [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"; }
Getting default value... Getting default value...
現在,讓我們運行另一個該值存在測試,理想情況下,甚至不應創建默認值:
在這個簡單的示例中,創建默認對象不會花費很多成本,因為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("john@gmail.com", "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("anna@gmail.com", "1234"); User result = Optional.ofNullable(user).orElse(user2); assertEquals(user2.getEmail(), result.getEmail());
這里 user 對象是空的,所以返回了作為默認值的 user2。
如果對象的初始值不是 null,那么默認值會被忽略:
User user = new User("john@gmail.com","1234"); User user2 = new User("anna@gmail.com", "1234"); User result = Optional.ofNullable(user).orElse(user2); assertEquals("john@gmail.com", 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("extra@gmail.com", "1234"); }
上面的代碼中,兩種方法都調用了 createNewUser() 方法,這個方法會記錄一個消息並返回 User 對象。
代碼輸出如下:
Using orElse Creating New User Using orElseGet Creating New User
由此可見,當對象為空而返回默認對象時,行為並無差異。
我們接下來看一個類似的示例,但這里 Optional 不為空:
@Test public void givenPresentValue_whenCompare_thenOk() { User user = new User("john@gmail.com", "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("anna@gmail.com", "1234"); String email = Optional.ofNullable(user) .map(u -> u.getEmail()).orElse("default@gmail.com"); 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("anna@gmail.com", "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("anna@gmail.com", "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("anna@gmail.com", "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("john@gmail.com", "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"));
a)不要將其用作類中的字段,因為它不可序列化
如果確實需要序列化包含Optional值的對象,則Jackson庫提供了將Optionals視為普通對象的支持。這意味着Jackson將空對象視為空,將具有值的對象視為包含該值的字段。可以在jackson-modules-java8項目中找到此功能。
b)不要將其用作構造函數和方法的參數,因為這會導致不必要的復雜代碼。
User user = new User("john@gmail.com", "1234", Optional.empty());
轉載鏈接:https://www.jianshu.com/p/362010f310b9