Java8(5):使用 Optional 處理 null

寫過 Java 程序的同窗,通常都遇到過 NullPointerException :) —— 爲了避免拋出這個異常,咱們便會寫以下的代碼:java

User user = getUserById(id);
if (user != null) {
    String username = user.getUsername();
    System.out.println("Username is: " + username); // 使用 username
}

可是不少時候,咱們可能會忘記寫 if (user != null) —— 若是在開發階段就發現那還好,可是若是在開發階段沒有測試到問題,等到上線卻出了 NullPointerException ... 畫面太美,我不敢繼續想下去。數據庫


爲了解決這種尷尬的處境,JDK 終於在 Java8 的時候加入了 Optional 類,查看 Optional 的 javadoc 介紹:app

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

這是一個能夠包含或者不包含非 null 值的容器。若是值存在則 isPresent()方法會返回 true,調用 get() 方法會返回該對象。函數

java.util.Optional<T>

JDK 提供三個靜態方法來構造一個 Optional
1.Optional.of(T value),該方法經過一個非 nullvalue 來構造一個 Optional,返回的 Optional 包含了 value 這個值。對於該方法,傳入的參數必定不能爲 null,不然便會拋出 NullPointerException測試

2.Optional.ofNullable(T value),該方法和 of 方法的區別在於,傳入的參數能夠爲 null —— 可是前面 javadoc 不是說 Optional 只能包含非 null 值嗎?咱們能夠看看 ofNullable 方法的源碼:this

Optional.ofNullable 方法

原來該方法會判斷傳入的參數是否爲 null,若是爲 null 的話,返回的就是 Optional.empty()spa

3.Optional.empty(),該方法用來構造一個空的 Optional,即該 Optional 中不包含值 —— 其實底層實現仍是 若是 Optional 中的 valuenull 則該 Optional 爲不包含值的狀態,而後在 API 層面將 Optional 表現的不能包含 null 值,使得 Optional 只存在 包含值不包含值 兩種狀態。code

Optional.empty() 的實現


前面 javadoc 也有提到,OptionalisPresent() 方法用來判斷是否包含值,get() 用來獲取 Optional 包含的值 —— 值得注意的是,若是值不存在,即在一個Optional.empty 上調用 get() 方法的話,將會拋出 NoSuchElementException 異常
咱們假設 getUserById 已是個客觀存在的不能改變的方法,那麼利用 isPresentget 兩個方法,咱們如今能寫出下面的代碼:對象

Optional<User> user = Optional.ofNullable(getUserById(id));
if (user.isPresent()) {
    String username = user.get().getUsername();
    System.out.println("Username is: " + username); // 使用 username
}

好像看着代碼是優美了點 —— 可是事實上這與以前判斷 null 值的代碼沒有本質的區別,反而用 Optional 去封裝 value,增長了代碼量。因此咱們來看看 Optional 還提供了哪些方法,讓咱們更好的(以正確的姿式)使用 Optionalblog

1.ifPresent

ifPresent

若是 Optional 中有值,則對該值調用 consumer.accept,不然什麼也不作。
因此對於上面的例子,咱們能夠修改成:

Optional<User> user = Optional.ofNullable(getUserById(id));
user.ifPresent(u -> System.out.println("Username is: " + u.getUsername()));

2.orElse

orElse

若是 Optional 中有值則將其返回,不然返回 orElse 方法傳入的參數。

User user = Optional
        .ofNullable(getUserById(id))
        .orElse(new User(0, "Unknown"));
        
System.out.println("Username is: " + user.getUsername());

3.orElseGet

orElseGet

orElseGetorElse 方法的區別在於,orElseGet 方法傳入的參數爲一個 Supplier 接口的實現 —— 當 Optional 中有值的時候,返回值;當 Optional 中沒有值的時候,返回從該 Supplier 得到的值。

User user = Optional
        .ofNullable(getUserById(id))
        .orElseGet(() -> new User(0, "Unknown"));
        
System.out.println("Username is: " + user.getUsername());

4.orElseThrow

orElseThrow

orElseThroworElse 方法的區別在於,orElseThrow 方法當 Optional 中有值的時候,返回值;沒有值的時候會拋出異常,拋出的異常由傳入的 exceptionSupplier 提供。

User user = Optional
        .ofNullable(getUserById(id))
        .orElseThrow(() -> new EntityNotFoundException("id 爲 " + id + " 的用戶沒有找到"));

舉一個 orElseThrow 的用途:在 SpringMVC 的控制器中,咱們能夠配置統一處理各類異常。查詢某個實體時,若是數據庫中有對應的記錄便返回該記錄,不然就能夠拋出 EntityNotFoundException ,處理 EntityNotFoundException 的方法中咱們就給客戶端返回Http 狀態碼 404 和異常對應的信息 —— orElseThrow 完美的適用於這種場景。

@RequestMapping("/{id}")
public User getUser(@PathVariable Integer id) {
    Optional<User> user = userService.getUserById(id);
    return user.orElseThrow(() -> new EntityNotFoundException("id 爲 " + id + " 的用戶不存在"));
}

@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<String> handleException(EntityNotFoundException ex) {
    return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}

5.map

map

若是當前 OptionalOptional.empty,則依舊返回 Optional.empty;不然返回一個新的 Optional,該 Optional 包含的是:函數 mapper 在以 value 做爲輸入時的輸出值。

String username = Optional.ofNullable(getUserById(id))
                        .map(user -> user.getUsername())
                        .orElse("Unknown")
                        .ifPresent(name -> System.out.println("Username is: " + name));

並且咱們能夠屢次使用 map 操做:

Optional<String> username = Optional.ofNullable(getUserById(id))
                                .map(user -> user.getUsername())
                                .map(name -> name.toLowerCase())
                                .map(name -> name.replace('_', ' '))
                                .orElse("Unknown")
                                .ifPresent(name -> System.out.println("Username is: " + name));

6.flatMap

flatMap

flatMap 方法與 map 方法的區別在於,map 方法參數中的函數 mapper 輸出的是值,而後 map 方法會使用 Optional.ofNullable 將其包裝爲 Optional;而 flatMap 要求參數中的函數 mapper 輸出的就是 Optional

Optional<String> username = Optional.ofNullable(getUserById(id))
                                .flatMap(user -> Optional.of(user.getUsername()))
                                .flatMap(name -> Optional.of(name.toLowerCase()))
                                .orElse("Unknown")
                                .ifPresent(name -> System.out.println("Username is: " + name));

7.filter

filter

filter 方法接受一個 Predicate 來對 Optional 中包含的值進行過濾,若是包含的值知足條件,那麼仍是返回這個 Optional;不然返回 Optional.empty

Optional<String> username = Optional.ofNullable(getUserById(id))
                                .filter(user -> user.getId() < 10)
                                .map(user -> user.getUsername());
                                .orElse("Unknown")
                                .ifPresent(name -> System.out.println("Username is: " + name));

有了 Optional,咱們即可以方便且優雅的在本身的代碼中處理 null 值,而再也不須要一昧經過容易忘記和麻煩的 if (object != null) 來判斷值不爲 null。若是你的程序還在使用 Java8 以前的 JDK,能夠考慮引入 Google 的 Guava 庫 —— 事實上,早在 Java6 的年代,Guava 就提供了 Optional 的實現。


號外:Java9 對 Optional 的加強
即將在今年 7 月到來的 JDK9 中,在 Optional 類中添加了三個新的方法:

  • public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

or 方法的做用是,若是一個 Optional 包含值,則返回本身;不然返回由參數 supplier 得到的 Optional

  • public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

ifPresentOrElse 方法的用途是,若是一個 Optional 包含值,則對其包含的值調用函數 action,即 action.accept(value),這與 ifPresent 一致;與 ifPresent 方法的區別在於,ifPresentOrElse 還有第二個參數 emptyAction —— 若是 Optional 不包含值,那麼 ifPresentOrElse 便會調用 emptyAction,即 emptyAction.run()

  • public Stream<T> stream()

stream 方法的做用就是將 Optional 轉爲一個 Stream,若是該 Optional 中包含值,那麼就返回包含這個值的 Stream;不然返回一個空的 StreamStream.empty())。

舉個例子,在 Java8,咱們會寫下面的代碼:

// 此處 getUserById 返回的是 Optional<User>
public List<User> getUsers(Collection<Integer> userIds) {
       return userIds.stream()
                .map(this::getUserById)      // 得到 Stream<Optional<User>>
                .filter(Optional::isPresent) // 去掉不包含值的 Optional,不然若是存在爲空的 Optional 下面的 get 會拋出異常
                .map(Optional::get)          // 變爲 Stream<User>
                .collect(Collectors.toList());
}

而有了 Optional.stream(),咱們就能夠將其簡化爲:

public List<User> getUsers(Collection<Integer> userIds) {
    return userIds.stream()
                .map(this::getUserById)    // 得到 Stream<Optional<User>>
                .flatMap(Optional::stream) // Stream 的 flatMap 方法將多個流合成一個流,若是 Optional 爲空則對應是空的 Stream,合併時會跳過
                .collect(Collectors.toList());
}
相關文章
相關標籤/搜索