1. 程式人生 > 程式設計 >Java8之熟透Optional

Java8之熟透Optional

一、使用Optional引言

1.1、程式碼問題引出

在寫程式的時候一般都遇到過 NullPointerException,所以經常會對程式進行非空的判斷:

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

為瞭解決這種尷尬的處境,JDK 終於在 Java8 的時候加入了 Optional 類,檢視 Optional 的 javadoc 介紹:

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() 方法會返回該物件。

1.2、解決進階

我們假設 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

還提供了哪些方法,讓我們更好的(以正確的姿勢)使用 Optional

二、Optional三個靜態構造方法

1)概述:

JDK 提供三個靜態方法來構造一個 Optional

  1. Optional.of(T value)

        public static <T> Optional<T> of(T value) {
            return new Optional<>(value);
        }
    複製程式碼

    該方法通過一個非 nullvalue 來構造一個 Optional,返回的 Optional 包含了 value 這個值。對於該方法,傳入的引數一定不能為 null,否則便會丟擲 NullPointerException

  2. Optional.ofNullable(T value)

        public static <T> Optional<T> ofNullable(T value) {
            return value == null ? empty() : of(value);
        }
    複製程式碼

    該方法和 of 方法的區別在於,傳入的引數可以為 null —— 但是前面 javadoc 不是說 Optional 只能包含非 null 值嗎?我們可以看看 ofNullable 方法的原始碼。

    原來該方法會判斷傳入的引數是否為 null,如果為 null 的話,返回的就是 Optional.empty()

  3. Optional.empty()

        public static<T> Optional<T> empty() {
            @SuppressWarnings("unchecked")
            Optional<T> t = (Optional<T>) EMPTY;
            return t;
        }
    複製程式碼

    該方法用來構造一個空的 Optional,即該 Optional 中不包含值 —— 其實底層實現還是 如果 Optional 中的 value 為 null 則該 Optional 為不包含值的狀態,然後在 API 層面將 Optional 表現的不能包含 null值,使得 Optional 只存在 包含值不包含值 兩種狀態。

2)分析:

前面 javadoc 也有提到,OptionalisPresent() 方法用來判斷是否包含值,get() 用來獲取 Optional 包含的值 —— 值得注意的是,如果值不存在,即在一個Optional.empty 上呼叫 get() 方法的話,將會丟擲 NoSuchElementException異常

3)總結:

1)Optional.of(obj): 它要求傳入的 obj 不能是 null 值的,否則還沒開始進入角色就倒在了 NullPointerException 異常上了. 2)Optional.ofNullable(obj): 它以一種智慧的,寬容的方式來構造一個 Optional 例項. 來者不拒,傳 null 進到就得到 Optional.empty(),非 null 就呼叫 Optional.of(obj). 那是不是我們只要用 Optional.ofNullable(obj) 一勞永逸,以不變應二變的方式來構造 Optional 例項就行了呢? 那也未必,否則 Optional.of(obj) 何必如此暴露呢,私有則可。

三、Optional常用方法詳解

3.1、Optional常用方法概述

  1. Optional.of(T t)

    將指定值用 Optional 封裝之後返回,如果該值為 null,則丟擲一個 NullPointerException 異常。

  2. Optional.empty()

    建立一個空的 Optional 例項。

  3. Optional.ofNullable(T t)

    將指定值用 Optional 封裝之後返回,如果該值為 null,則返回一個空的 Optional 物件。

  4. isPresent

    如果值存在返回true,否則返回false

  5. ifPresent

    如果Optional例項有值則為其呼叫consumer,否則不做處理。 要理解ifPresent方法,首先需要了解Consumer類。簡答地說,Consumer類包含一個抽象方法。該抽象方法對傳入的值進行處理,但沒有返回值。 Java8支援不用介面直接通過lambda表示式傳入引數。 如果Optional例項有值,呼叫ifPresent()可以接受介面段或lambda表示式。

  6. Optional.get()

    如果該值存在,將該值用 Optional 封裝返回,否則丟擲一個 NoSuchElementException 異常。

  7. orElse(T t)

    如果呼叫物件包含值,返回該值,否則返回t。

  8. orElseGet(Supplier s)

    如果呼叫物件包含值,返回該值,否則返回 s 獲取的值。

  9. orElseThrow()

    它會在物件為空的時候丟擲異常。

  10. map(Function f)

    如果值存在,就對該值執行提供的 mapping 函式呼叫。

  11. flatMap(Function mapper)

    如果值存在,就對該值執行提供的mapping 函式呼叫,返回一個 Optional 型別的值,否則就返回一個空的 Optional 物件。

3.2、Optional常用方法詳解

3.2.1、ifPresent
    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }
複製程式碼

如果 Optional 中有值,則對該值呼叫 consumer.accept,否則什麼也不做。 所以對於引言上的例子,我們可以修改為:

Optional<User> user = Optional.ofNullable(getUserById(id));
user.ifPresent(u -> System.out.println("Username is: " + u.getUsername()));
複製程式碼
3.2.2、orElse
    public T orElse(T other) {
        return value != null ? value : other;
    }
複製程式碼

如果 Optional 中有值則將其返回,否則返回 orElse 方法傳入的引數。

User user = Optional
        .ofNullable(getUserById(id))
        .orElse(new User(0,"Unknown"));
        
System.out.println("Username is: " + user.getUsername());
複製程式碼
3.2.3、orElseGet
    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }
複製程式碼

orElseGetorElse 方法的區別在於,orElseGet 方法傳入的引數為一個 Supplier介面的實現 —— 當 Optional 中有值的時候,返回值;當 Optional 中沒有值的時候,返回從該 Supplier 獲得的值。

User user = Optional
        .ofNullable(getUserById(id))
        .orElseGet(() -> new User(0,"Unknown"));
        
System.out.println("Username is: " + user.getUsername());
複製程式碼
3.2.4、orElseThrow
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
複製程式碼

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

舉例說明:

​ 在 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);
}
複製程式碼
3.2.5、map
    public<U> Optional<U> map(Function<? super T,? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
複製程式碼

如果當前 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));
複製程式碼
3.2.6、flatMap
    public<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }
複製程式碼

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));
複製程式碼
3.2.7、filter
    public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }
複製程式碼

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使用示例

4.1、使用展示一

當 user.isPresent() 為真,獲得它關聯的 orders的對映集合,為假則返回一個空集合時,我們用上面的 orElse,orElseGet 方法都乏力時,那原本就是 map 函式的責任,我們可以這樣一行:

return user.map(u -> u.getOrders()).orElse(Collections.emptyList())
 
//上面避免了我們類似 Java 8 之前的做法
if(user.isPresent()) {
  return user.get().getOrders();
} else {
  return Collections.emptyList();
}
複製程式碼

map 是可能無限級聯的,比如再深一層,獲得使用者名稱的大寫形式:

return user.map(u -> u.getUsername())
           .map(name -> name.toUpperCase())
           .orElse(null);
複製程式碼

以前的做法:

User user = .....
if(user != null) {
  String name = user.getUsername();
  if(name != null) {
    return name.toUpperCase();
  } else {
    return null;
  }
} else {
  return null;
}
複製程式碼

filter() :如果有值並且滿足條件返回包含該值的Optional,否則返回空Optional。

Optional<String> longName = name.filter((value) -> value.length() > 6);  
System.out.println(longName.orElse("The name is less than 6 characters")); 
複製程式碼