Java8之熟透Optional

1、使用Optional引言

1.一、代碼問題引出

在寫程序的時候通常都遇到過 NullPointerException,因此常常會對程序進行非空的判斷:java

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() 方法會返回該對象。app

1.二、解決進階

咱們假設 getUserById 已是個客觀存在的不能改變的方法,那麼利用 isPresentget 兩個方法,咱們如今能寫出下面的代碼:less

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函數

2、Optional三個靜態構造方法

1)概述:ui

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

  1. Optional.of(T value)spa

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

    該方法經過一個非 nullvalue 來構造一個 Optional,返回的 Optional 包含了 value 這個值。對於該方法,傳入的參數必定不能爲 null,不然便會拋出 NullPointerExceptioncode

  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) 何須如此暴露呢, 私有則可。

3、Optional經常使用方法詳解

3.一、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.二、Optional經常使用方法詳解

3.2.一、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.二、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.三、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.四、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.五、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.六、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.七、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));
複製代碼

4、Optional使用示例

4.一、使用展現一

當 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")); 
複製代碼
相關文章
相關標籤/搜索