1. 程式人生 > >jdk8 Optional 的正確姿勢

jdk8 Optional 的正確姿勢

我們知道 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 時, 就得開始檢視自己了

  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 { ... } 幾中應用方式.

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

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() 判斷, 真時處理值, 假時什麼也不做.

相關推薦

Centos7 安裝 jdk8正確姿勢 實踐筆記

百度統計 [removed] var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.

jdk8 Optional正確姿勢

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

[轉] 使用 Java8 Optional正確姿勢

rac other 不包含 pty zab optional 集合 list posit [From] https://unmi.cc/proper-ways-of-using-java8-optional/ 我們知道 Java 8 增加了一些很有用的 API, 其中一

使用 Java8 Optional正確姿勢

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

Android開發入門的正確姿勢,你get到了嗎?

開源 如何 com 正常 它的 接收 應用 切換 角度 在進行Android開發之前,我們先了解一下Android的生態圈現狀。Android系統是開源的,任何手機廠商和開發者都有權限去修改系統源代碼,定制專屬的系統。 這就產生了一個問題,不同手機廠商之間的ROM可能無法安

UITableViewCell嵌套UITableView的正確姿勢

views ble wce items repl ride ret 麻煩 sel 內嵌UiTableView的高度計算起來太麻煩了,如何解決,就是把二級TableVIew裏面的model item做到一級,然後對不同的item類型做不同的Cell,這樣就Ok

Android玩轉百度地圖Sha1獲取正確姿勢

views num adl 定位 提示 合作 動態 例如 登錄 場景一 由於近期項目鐘要用到定位功能因此肯定須要用到地圖以及地位功能,相信大家也知道眼下國內比較出名的地圖像百度、高德、騰訊等這些還是用到比較多的。於是思考了一下決定還是用百

HDFS數據遷移目錄到正確姿勢

pre -h -s dir 目錄 關閉 所有 white cdh 添加了一塊硬盤,原來的DataNode已經把原有的硬盤占滿;怎麽辦,想要把舊有的數據遷移到新的硬盤上面; 1. 在CDH中修改目錄(在HDFS組件中搜索.dir),本例中,新加的硬盤掛載在/data上面,Na

fetch獲取json的正確姿勢

改進 key ret per property json cat cnblogs pan fetch要求參數傳遞,遇到請求無法正常獲取數據,網上其他很多版本類似這樣: fetch(url ,{ method: ‘POST‘, hea

Intellij IDEA中使用Protobuf的正確姿勢

hub pom.xml文件 art -c github es2017 生成 out -i 一、.proto文件語法高亮顯示 需要安裝Protobuf Support插件 依次點擊Intellij中的“File”-->"Settings"-->"Pl

CentOS修改root密碼的正確姿勢

修改root口令寫在前面 在生產或者日常使用中,偶爾會出現root密碼忘記的情況。這時如何修改root密碼成了一個工程師需要關註的小知識。下面詳細說一下如何正確的修改root密碼。如果root密碼丟失,遠程操作使用正常辦法是無法解決的。所以要修改root密碼服務器必須在你身邊。修改root密碼原理:在單用戶

ASP.net Core 2.0 EF (mysql) 遷移 正確姿勢

com update reat .com ttr 數據庫 pda creat mic 用於進行遷移的 Entity Framework Core NuGet 包 註意:必須通過編輯 .csproj 文件來安裝此包;不能使用 install-package 命令或程序包管理器

身份證掃描識別/身份證OCR識別的正確姿勢,你get到了嗎?

視頻流 開發包 掃描識別 出錯 應用 左右 信息 設備 ucs 自從國家規定電信實名制之後,實名制已經推廣到各個領域:辦理通信業務需要實名制、銀行開戶需要實名制、移動支付需要實名制,就連註冊個自媒體賬戶都需要實名制。 而實名制的背後,就是身份證信息的采集和錄入驗證。 傳統的

創建單例的正確姿勢

attribute ati tro margin 直接 tab amp har ron 方法 1:   聲明 :+ (instancetype)sharedInstance 單例方法   重寫:+ (instancetype)allocWithZone:(struct _

溝通技巧系列 - 給予和接受反饋的正確姿勢

準備 你會 在哪裏 美的 剛才 方式 辦公室 出現 了解 給予反饋 “績效評估” - 往往是不論普通員工還是管理者都不願意做的事情。 世界各地的員工和管理人員都對這種儀式感到恐懼,其中存在的主要問題是:我們已經將反饋的給予和接受程序化了。我們暗中觀察員工的日常

什麽是開始學習英文的正確姿勢

聽說 課本 篩選 狀態 水平 評估 一位 通過 媽媽 經常被人問如何學習英文,通過實踐我認為找到了一條可以學好英文的可行之路,現在我在這條路的入口打著火把給想走這條路的人照個亮,分享一下自己的學習經驗。 學習前英文水平想認真學英文但又不知道從哪裏開始,英文聽說讀寫能力停留在

用VSCode寫python的正確姿勢

下載地址 菜單 調試 alt+ install 如果 ++ top 函數名 最近在學習python,之前一直用notepad++作為編輯器,偶然發現了VScode便被它的顏值吸引。用過之後發現它啟動快速,插件豐富,下載安裝後幾乎不用怎麽配置就可以直接使用,而且還支持mark

雲服務器綁定主機名的正確姿勢

雲主機、oracle監聽、linux故障雲服務器綁定主機名的正確姿勢 一直以來,我在做方案的時候,都不推薦在雲服務器上安裝oracle。可隨著宣傳的深入,偏偏就有人要這樣幹,前天,就有要求我在某度雲安裝oracle rac集群;我確實不知道怎麽解決共享存儲及多網卡。 一個很久不理我的家夥,突然給我電話,說安裝

入門Promise的正確姿勢

說過 一般來說 完全 第一個 失敗 測試 也會 沒有 輸出 Promise是異步編程的一種解決方案,從語法上說,Promise是一個對象,從它可以獲取異步操作的消息。 Promise的基本用法 Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve

使用 win10 的正確姿勢 (第二版)

崩潰 lis 第一篇 com 分開 下載 不同 AR str 文章為本人原創,轉載請註明出處,謝謝。 17年9月初,寫了第一篇《使用 win10 的正確姿勢》,而現在半年多過去,文章更新了一些,主要是桌面的變化。 一. 重新定義桌面 我的桌面: 將桌面定義為:臨時文件夾。