天天看點

Jdk8 Optional 的使用

在實習過程中,閱讀項目源碼的時候看到了Optional的用法,将傳回的類進行包裝,調用Optional類中的一些方法。不禁利用一些業餘時間,好好學習一下jdk1.8帶給我們強大的用法。總之就是,簡化了之前代碼的書寫,使用函數式程式設計,不用反複去檢驗傳回值為null的情況,總之,誰用誰知道,好好學吧

我們知道 Java 8 增加了一些很有用的 API, 其中一個就是 Optional. 如果對它不稍假探索, 隻是輕描淡寫的認為它可以優雅的解決 NullPointException 的問題, 于是代碼就開始這麼寫了

  1. Optional<User> user = ……
  2. if (user.isPresent()) {
  3. return user.getOrders();
  4. } else {
  5. return Collections.emptyList();
  6. }

那麼不得不說我們的思維仍然是在原地踏步, 隻是本能的認為它不過是 User 執行個體的包裝, 這與我們之前寫成

  1. User user = .....
  2. if (user != null) {
  3. return user.getOrders();
  4. } else {
  5. return Collections.emptyList();
  6. }

實質上是沒有任何分别. 這就是我們将要講到的使用好 Java 8 Optional 類型的正确姿勢.

在裡約奧運之時, 新聞一再提起五星紅旗有問題, 可是我怎麼看都看不出來有什麼問題, 後來才道是小星星膜拜中央的姿勢不對. 是以我們千萬也别對自己習以為常的事情覺得理所當然, 絲毫不會覺得有何不妥, 換句話說也就是當我們切換到 Java 8 的 Optional 時, 不能繼承性的對待過往 null 時的那種思維, 應該掌握好新的, 正确的使用 Java 8 Optional 的正确姿勢.

直白的講, 當我們還在以如下幾種方式使用 Optional 時, 就得開始檢視自己了

  1. 調用 isPresent()  方法時
  2. 調用 get()  方法時
  3. Optional 類型作為類/執行個體屬性時
  4. Optional 類型作為方法參數時

isPresent() 與 obj != null 無任何分别, 我們的生活依然在步步驚心. 而沒有 isPresent() 作鋪墊的 get() 調用在 IntelliJ IDEA 中會收到告警

Reports calls to java.util.Optional.get() without first checking with a isPresent() call if a value is available. If the Optional does not contain a value, get() will throw an exception. (調用 Optional.get() 前不事先用 isPresent() 檢查值是否可用. 假如 Optional 不包含一個值, get() 将會抛出一個異常)

把 Optional 類型用作屬性或是方法參數在 IntelliJ IDEA 中更是強力不推薦的

Reports any uses of java.util.Optional<T>, java.util.OptionalDouble, java.util.OptionalInt, java.util.OptionalLong or com.google.common.base.Optional as the type for a field or a parameter. Optional was designed to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result”. Using a field with type java.util.Optional is also problematic if the class needs to be Serializable, which java.util.Optional is not. (使用任何像 Optional 的類型作為字段或方法參數都是不可取的. Optional 隻設計為類庫方法的, 可明确表示可能無值情況下的傳回類型. Optional 類型不可被序列化, 用作字段類型會出問題的)

是以 Optional 中我們真正可依賴的應該是除了 isPresent() 和 get() 的其他方法:

  1. public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
  2. public T orElse(T other)
  3. public T orElseGet(Supplier<? extends T> other)
  4. public void ifPresent(Consumer<? super T> consumer)
  5. public Optional<T> filter(Predicate<? super T> predicate)
  6. public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
  7. public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X

我略有自信的按照它們大概使用頻度對上面的方法排了一下序.

先又不得不提一下 Optional 的三種構造方式: Optional.of(obj) ,   Optional.ofNullable(obj) 和明确的 Optional.empty()

Optional.of(obj) : 它要求傳入的 obj 不能是 null 值的, 否則還沒開始進入角色就倒在了 NullPointerException 異常上了.

Optional.ofNullable(obj) : 它以一種智能的, 寬容的方式來構造一個 Optional 執行個體. 來者不拒, 傳 null 進到就得到 Optional.empty() , 非 null 就調用 Optional.of(obj) .

那是不是我們隻要用 Optional.ofNullable(obj) 一勞永逸, 以不變應二變的方式來構造 Optional 執行個體就行了呢? 那也未必, 否則 Optional.of(obj) 何必如此暴露呢, 私有則可?

我本人的觀點是:  1. 當我們非常非常的明确将要傳給 Optional.of(obj) 的 obj 參數不可能為 null 時, 比如它是一個剛 new 出來的對象( Optional.of(new User(…)) ), 或者是一個非 null 常量時;  2. 當想為 obj 斷言不為 null 時, 即我們想在萬一 obj 為 null 立即報告 NullPointException 異常, 立即修改, 而不是隐藏空指針異常時, 我們就應該果斷的用 Optional.of(obj) 來構造 Optional 執行個體, 而不讓任何不可預計的 null 值有可乘之機隐身于 Optional 中.

現在才開始怎麼去使用一個已有的 Optional 執行個體, 假定我們有一個執行個體 Optional<User> user , 下面是幾個普遍的, 應避免 if(user.isPresent()) { … } else { … } 幾中應用方式.

存在即傳回, 無則提供預設值

  1. return user.orElse( null); //而不是 return user.isPresent() ? user.get() : null;
  2. return user.orElse(UNKNOWN_USER);

存在即傳回, 無則由函數來産生

return user.orElseGet(() -> fetchAUserFromDatabase()); //而不要 return user.isPresent() ? user: fetchAUserFromDatabase();           

存在才對它做點什麼

  1. user.ifPresent(System.out::println);
  2. //而不要下邊那樣
  3. if (user.isPresent()) {
  4. System.out.println(user.get());
  5. }

map 函數隆重登場

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

  1. return user.map(u -> u.getOrders()).orElse(Collections.emptyList())
  2. //上面避免了我們類似 Java 8 之前的做法
  3. if(user.isPresent()) {
  4. return user.get().getOrders();
  5. } else {
  6. return Collections.emptyList();
  7. }

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

  1. return user.map(u -> u.getUsername())
  2. .map(name -> name.toUpperCase())
  3. .orElse( null);

這要擱在以前, 每一級調用的展開都需要放一個 null 值的判斷

  1. User user = .....
  2. if(user != null) {
  3. String name = user.getUsername();
  4. if(name != null) {
  5. return name.toUpperCase();
  6. } else {
  7. return null;
  8. }
  9. } else {
  10. return null;
  11. }

針對這方面 Groovy 提供了一種安全的屬性/方法通路操作符 ?.

user?.getUsername()?.toUpperCase();           

Swift 也有類似的文法, 隻作用在  Optional 的類型上.

用了 isPresent() 處理 NullPointerException 不叫優雅, 有了  orElse, orElseGet 等, 特别是 map 方法才叫優雅.

其他幾個, filter() 把不符合條件的值變為 empty() ,   flatMap() 總是與 map() 方法成對的,   orElseThrow() 在有值時直接傳回, 無值時抛出想要的異常.

一句話小結: 使用 Optional 時盡量不直接調用 Optional.get() 方法, Optional.isPresent() 更應該被視為一個私有方法, 應依賴于其他像 Optional.orElse() , Optional.orElseGet() , Optional.map() 等這樣的方法.

最後, 最好的了解 Java 8 Optional 的方法莫過于看它的源代碼java.util.Optional , 閱讀了源代碼才能真真正正的讓你解釋起來最有底氣, Optional 的方法中基本都是内部調用   isPresent() 判斷, 真時處理值, 假時什麼也不做.