首頁  >  文章  >  Java  >  使用 Java8 Optional 的正確姿勢

使用 Java8 Optional 的正確姿勢

黄舟
黄舟原創
2017-01-18 15:24:461395瀏覽

Java程式設計語言

java 是一種可以撰寫跨平台應用軟體的物件導向的程式設計語言,是由Sun Microsystems公司於1995年5月推出的Java程式設計語言和Java平台(即JavaEE(j2ee) , JavaME(j2me), JavaSE(j2se))的總稱。


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

Optional<User> user = ……
if (user.isPresent()) {
return user.getOrders();
} else {
return Collections.emptyList();
}

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

User user = …..
if (user != null) {
return user.getOrders();
} else {
return Collections.emptyList();
}

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

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

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

調用 isPresent() 方法時

調用 get() 方法時

Optional 類型作為類別/實例屬性時

Optional 類型作為方法參數時

isPresent() 與 obj 類型作為方法參數時

isPresent() 與 obj 類型作為方法參數時

isPresent() 與 obj 類型!而沒有 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 类型不可被序列化, 用作字段类型会出问题的)

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

public Optional map(Function super T, ? extends U> mapper)

public T orElse(T orElse )

public T orElseGet(Supplier extends T> other)

public void ifPresent(Consumer super T> consumer)

public Optional filter(Predicate) U> Optional flatMap(Function super T, Optional> mapper)

public 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(objofNullable(obj): 它以一種智能的, 寬容的方式來構造一個Optional 實例. 來者不拒, 傳null 進到就得到 Optional.empty(), 非null 就調用 Optional.of(obj).

那是不是我們只要用 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, 下面是幾個普遍的, 應避免 if(user.isPresent()) { ... } else { ... } 幾中應用方式.

🎜存在即回傳, 無則提供預設值🎜
return user.orElse(null);  //而不是 return user.isPresent() ? user.get() : null;
return user.orElse(UNKNOWN_USER);
🎜存在即回傳, 無則由函數來產生🎜
return user.orElseGet(() -> fetchAUserFromDatabase()); //而不要 return user.isPresent() ? user: fetchAUserFromDatabase();

存在才对它做点什么

user.ifPresent(System.out::println);

//而不要下边那样
if (user.isPresent()) {
  System.out.println(user.get());
}

map 函数隆重登场

当 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);

这要搁在以前, 每一级调用的展开都需要放一个 null 值的判断

User user = .....
if(user != null) {
  String name = user.getUsername();
  if(name != null) {
    return name.toUpperCase();
  } else {
    return null;
  }
} else {
  return null;
}

针对这方面 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() 判断, 真时处理值, 假时什么也不做.

以上就是使用 Java8 Optional 的正确姿势的内容,更多相关内容请关注PHP中文网(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn