# 六角鼠年鐵人賽 Week 18 - Spring Boot - 番外篇 Java 8 Optional Tutorial ==大家好,我是 "為了拿到金角獎盃而努力著" 的文毅青年 - Kai== ## 劇中佳句 LIFE OF PI :::info The most important thing is not to despair. ::: ## 主題 - Optional 類別 Java 工程師在開發程式邏輯時,最惱人也是最容易忽略的便是傳值過程中發生的 **NullPointerException** - **有可能是來自於抓取資料的意外** - **有可能是參數已然在其他流程中被轉換、覆蓋、重新定義** - **有可能單純忽略了 Null 的可能性** 相信許多工程師都有上述經驗,面對這些狀況可以用 Try Catch 作處理,或是乾脆的 Hard code 形式用 IF 判斷式跳過。 但這些解決法都會讓程式碼架構變得醜陋,滿滿的 Try Catch 和 IF 僅是為了判斷 Null 的狀況,無形中成為了開發以及維運上的酸痛貼布,你無法去除礙眼的它們,只能期待哪一天這問題可被忽略便能把它撕下來。 而那一天真的來了!! **Optional 類別** 正是 Java 8 橫空出世的一個嶄新的類別,專門用來處理惱人的 Null 處理。 且 Optional 能夠完美與 Stream 整合應用,配合 Lambda 的開發優勢下,對於 Null 的處理也更簡單與方便。 只要說到 Optional 類別,一定要知道就是用來處理 Null 的專門類別。 ## 常用方法介紹 由於 Optional 的類別方法不多,因此以下會逐一介紹與基本應用方式。 ### 建立 Optional 類別實體的方法 empty(), of(), ofNullable ![](https://i.imgur.com/HqT7PNo.png) 如圖所示,Optional 的物件需要這三個 static 方法進行實體的建立,而三個方法各自有輸入、輸出的條件。 - **empty()** - 不需要輸入任何參數 - 即返回一個空的 Optional 物件實體 - **of()** - 需要輸入參數,參數可以為任何非 Null 的物件 - 即返回一個包含該物件的 Optional 物件實體 - **ofNullable()** - 需要輸入參數 - 參數為 Null: 即返回一個空的 Optional 物件實體 - 參數為 任何非 Null 的物件: 含該物件的 Optional 物件實體 **範例** ```java= /*** empty(), of(), ofNullable() ***/ Optional<Object> opt1_1 = Optional.empty(); Optional<Object> opt1_2 = Optional.of("String Optional Object"); //Optional<Object> opt1_3 = Optional.of(null); 會導致 NullPointerException Optional<Object> opt1_4 = Optional.ofNullable(null); Optional<Object> opt1_5 = Optional.ofNullable(new String("String Optional Object")); System.out.println("opt1_1 :" + opt1_1.toString()); System.out.println("opt1_2 :" + opt1_2.toString()); //System.out.println("opt1_3 :" + opt1_3.toString()); System.out.println("opt1_4 :" + opt1_4.toString()); System.out.println("opt1_5 :" + opt1_5.toString()); ``` **輸出** ``` opt1_1 :Optional.empty opt1_2 :Optional[String Optional Object] opt1_4 :Optional.empty opt1_5 :Optional[String Optional Object] ``` > 上述的例子中可以清楚看到,若在 of() 中放入 null 就會導致錯誤的發生,因此在使用上,多半會直接使用 ofNullable 替代 ### 判斷是否有值 isPresent(), ifPresent() 在 Optional 物件中也有提供方法讓開發者進行實體內容進行確認,那就是 isPresent(),而 ifPresent() 則是 isPresent() 的 Lambda 表示式方法,能夠在內部類別方法直接針對 isPresent() 會回傳 True 的物件內容進行下一步處理,大大地增加開發效益。 - **isPresent()** - 不須輸入參數 - 僅以 Optional 是否為 empty 做判斷 - 若為 empty Optional,即回傳 false - 若不為 empty Optional,即回傳 true - **ifPresent()** - 需 Override 其內部類別方法 - 參數會自動由 Customer 物件帶入 - 會自動使用 isPresent() 判別,若回傳為 true 則接續使用 Override 的內部類別方法;若回傳為 false 則不做任何動作 - 不會回傳任何東西,ifPresent 的方法為 void > 簡單來說: > isPresent 就是用做簡易判斷 Optional 是否有任何內容物在裏頭 > ifPresent 則是用來將 **後續動作** 與 **判定動作** 結合的方法,讓開發達到更省力的方式 **範例** ```java= /*** isPresent, ifPresent ***/ Optional<Object> opt2_1 = Optional.empty(); Optional<Object> opt2_2 = Optional.ofNullable(new String("String Optional Object")); Optional<Object> opt2_3 = Optional.ofNullable(new String()); Optional<Object> opt2_4 = Optional.ofNullable(null); System.out.println("opt2_1 isPresent :" + opt2_1.isPresent()); opt2_1.ifPresent(value->{ System.out.println("opt2_1 ifPresent"); }); System.out.println("opt2_2 isPresent :" + opt2_2.isPresent()); opt2_2.ifPresent(value->{ System.out.println("opt2_2 ifPresent"); }); System.out.println("opt2_3 isPresent :" + opt2_3.isPresent()); opt2_3.ifPresent(value->{ System.out.println("opt2_3 ifPresent"); }); System.out.println("opt2_4 isPresent :" + opt2_4.isPresent()); opt2_4.ifPresent(value->{ System.out.println("opt2_4 ifPresent"); }); ``` **輸出** ``` opt2_1 isPresent :false opt2_2 isPresent :true opt2_2 ifPresent opt2_3 isPresent :true opt2_3 ifPresent opt2_4 isPresent :false ``` > 由上述例子可以發現,當 isPresent() 判斷為 false 之後,ifPresent() 就不會進行 Override 的內部類別方法,這讓開發人員在使用上不再需要為 Null 的事情操心,專門交給 Optional 類別來阻擋即可 ### 取值 - **get()** - 取得 Optional 物件中的值 > 通常會推薦使用 **orElse()** 等方式處理,因為在沒有放入任何東西的情況下,會直接導致 **java.util.NoSuchElementException** 錯誤的發生 ### 判斷有無值的處理方法 orElse(), orElseGet(), orElseThrow() 這三個方法其實是類似的,主要在於 orElse(),而 orElseGet() 是 orElse() 的衍生,orElseThrow() 則是用來處理 Exception 的狀況。 - **orElse()** - 須輸入參數 - 當 Optional 不為 empty 時,即回傳 "原本" 的物件內容 - 當 Optional 為 empty 時,即回傳 "輸入" 的物件內容 - **orElseGet()** - 須輸入 Supplier 物件 - 其餘與 orElse 相同 - **orElseThrow()** - 不須輸入參數 - 當 Optional 不為 empty 時,即回傳物件內容 - 當 Optional 為 empty 時,則拋出 Supplier Exception **範例** ```java= /*** orElse(), orElseGet(), orElseThrow() ***/ Optional<Integer> opt3_1 = Optional.empty(); Optional<Integer> opt3_2 = Optional.ofNullable(100); Optional<Integer> opt3_3 = Optional.ofNullable(null); System.out.println("opt3_1 orElse() :" + (opt3_1.orElse(1000) == 100)); System.out.println("opt3_1 orElse() :" + (opt3_1.orElse(1000) == 1000)); System.out.println("opt3_1 orElseGet() :" + (opt3_1.orElseGet(()->{ return 1000;}) == 100)); System.out.println("opt3_1 orElseGet() :" + (opt3_1.orElseGet(()->{ return 1000;}) == 1000)); try { System.out.println("opt3_1 orElseThrow() :" + opt3_1.orElseThrow(() -> { return new RuntimeException("opt3_1 orElseThrow() Exception"); })); } catch (RuntimeException e){ System.out.println(e.toString()); } System.out.println("opt3_2 orElse() :" + (opt3_2.orElse(1000) == 100)); System.out.println("opt3_2 orElse() :" + (opt3_2.orElse(1000) == 1000)); System.out.println("opt3_2 orElseGet() :" + (opt3_2.orElseGet(()->{ return 1000;}) == 100)); System.out.println("opt3_2 orElseGet() :" + (opt3_2.orElseGet(()->{ return 1000;}) == 1000)); try { System.out.println("opt3_2 orElseThrow() :" + opt3_2.orElseThrow(() -> { return new RuntimeException("opt3_2 orElseThrow() Exception"); })); } catch (RuntimeException e){ System.out.println(e.toString()); } System.out.println("opt3_3 orElse() :" + (opt3_3.orElse(1000) == 100)); System.out.println("opt3_3 orElse() :" + (opt3_3.orElse(1000) == 1000)); System.out.println("opt3_3 orElseGet() :" + (opt3_3.orElseGet(()->{ return 1000;}) == 100)); System.out.println("opt3_3 orElseGet() :" + (opt3_3.orElseGet(()->{ return 1000;}) == 1000)); try { System.out.println("opt3_3 orElseThrow() :" + opt3_3.orElseThrow(() -> { return new RuntimeException("opt3_3 orElseThrow() Exception"); })); } catch (RuntimeException e){ System.out.println(e.toString()); } ``` **輸出** ``` opt3_1 orElse() :false opt3_1 orElse() :true opt3_1 orElseGet() :false opt3_1 orElseGet() :true java.lang.RuntimeException: opt3_1 orElseThrow() Exception opt3_2 orElse() :true opt3_2 orElse() :false opt3_2 orElseGet() :true opt3_2 orElseGet() :false opt3_2 orElseThrow() :100 opt3_3 orElse() :false opt3_3 orElse() :true opt3_3 orElseGet() :false opt3_3 orElseGet() :true java.lang.RuntimeException: opt3_3 orElseThrow() Exception ``` > 由上述例子可以看到,當 Optional 為 empty 時,會自動存放輸入的參數,因此原先預設為 100 的 Optional 3_2 將不會存放 1000,因此在比對結果時,僅會與 100 的判斷式得出 TRUE。 > 而 empty Optional 則會因為沒有任何預設內容,而將輸入的參數給存放,因此在 Optional 3_1 和 3_3 就會存放 1000,並與 1000 的判斷式得出 TRUE。 > 同時也會讓 orElseThrow() 拋出 RuntimeException 中的 NullPointerException ### 判斷式 filter() 與 orElse() 方法不同,不因 Optional 物件本身為 empty 而增添新內容給它,是一個專心負責判斷式的方法 - **filter()** - 無輸入參數 - 若物件內容與 Override 的內部類別方法判斷式相等,則回傳原先的物件內容 - 若不相等,則回傳一個新的 empty Optional 物件內容 **範例** ```java= /*** filter() ***/ Optional<Integer> opt4_1 = Optional.empty(); Optional<Integer> opt4_2 = Optional.ofNullable(100); Optional<Integer> opt4_3 = Optional.ofNullable(null); try { System.out.println("filter_4_1 case1 :" + opt4_1.filter((a) -> a == null)); System.out.println("filter_4_1 case2 :" + opt4_1.filter((a) -> a == 100)); System.out.println("filter_4_1 case3 :" + opt4_1.filter((a) -> a == 1000)); } catch (Exception e) { System.out.println(e); } try { System.out.println("filter_4_2 case1 :" + opt4_2.filter((a) -> a == null)); System.out.println("filter_4_2 case2 :" + opt4_2.filter((a) -> a == 100)); System.out.println("filter_4_2 case3 :" + opt4_2.filter((a) -> a == 1000)); } catch (Exception e) { System.out.println(e); } try { System.out.println("filter_4_3 case1 :" + opt4_3.filter((a) -> a == null)); System.out.println("filter_4_3 case2 :" + opt4_3.filter((a) -> a == 100)); System.out.println("filter_4_3 case3 :" + opt4_3.filter((a) -> a == 1000)); } catch (Exception e) { System.out.println(e); } ``` **輸出** ``` filter_4_1 case1 :Optional.empty filter_4_1 case2 :Optional.empty filter_4_1 case3 :Optional.empty filter_4_2 case1 :Optional.empty filter_4_2 case2 :Optional[100] filter_4_2 case3 :Optional.empty filter_4_3 case1 :Optional.empty filter_4_3 case2 :Optional.empty filter_4_3 case3 :Optional.empty ``` > 從上述例子可以看到,原先為 empty 的 Optional 不會因為判斷式 True or False 受到影響,因為本身就是沒有任何內容的物件。 > 但原先有物件內容的 Optional 則因為判斷式 false,導致其回傳了一個 empty Optional > 上述若用來做判斷的參數為 Null 則會發生 NullPointerException > ### 支援 map() 與多層次的 flatMap() 功能 在 Stream 的文章中 Kai 有在應用層面提到一點,實際上關於這兩個方法都是在幫助 Optional 和 Stream 處理各種陣列或集合物件的迴圈作業。 而其中的差別又屬於 flatMap() 專用於多層次的資料架構上,何謂多層次的資料架構上呢? 簡單的說就是那些在集合物件中的集合物件,傳統上不能用一層迴圈可以處理的範疇都會稱作多層次的資料架構。這類型的集合物件我們就可以透過 flatMap() 幫忙完成其中內容的檢索。 在使用上可以發現到,用回傳值來看,flatMap() 已經將原先多層次的資料集合處理為單層次的了,而 map() 回傳的物件還是與原先相同的多層次。 **範例** ```java= /*** flatMap(), map() ***/ Optional<String> opt5_1 = Optional.ofNullable("test"); Optional<String> opt5_2 = Optional.ofNullable(null); Optional<Optional<String>> opt5_3 = Optional.of(Optional.ofNullable("test")); Optional<Optional<String>> opt5_4 = Optional.of(Optional.ofNullable(null)); //Optional<Optional<String>> opt5_5 = Optional.of(null); System.out.println("opt5_1 map() :" + opt5_1.map(String::toUpperCase)); System.out.println("opt5_1 flatMap() :" + opt5_1.flatMap(s -> Optional.of("TEST"))); System.out.println("opt5_2 map() :" + opt5_2.map(String::toUpperCase)); System.out.println("opt5_2 flatMap() :" + opt5_2.flatMap(s -> Optional.of("TEST"))); System.out.println("opt5_3 map() :" + opt5_3.map(s -> Optional.of("TEST"))); System.out.println("opt5_3 flatMap() :" + opt5_3.flatMap(s -> Optional.of("TEST"))); System.out.println("opt5_4 map() :" + opt5_4.map(s -> Optional.of("TEST"))); System.out.println("opt5_4 flatMap() :" + opt5_4.flatMap(s -> Optional.of("TEST"))); //System.out.println("opt5_5 map() :" + opt5_5.map(s -> Optional.of("TEST"))); //System.out.println("opt5_5 flatMap() :" + opt5_5.flatMap(s -> Optional.of("TEST"))); ``` **輸出** ``` opt5_1 map() :Optional[TEST] opt5_1 flatMap() :Optional[TEST] opt5_2 map() :Optional.empty opt5_2 flatMap() :Optional.empty opt5_3 map() :Optional[Optional[TEST]] opt5_3 flatMap() :Optional[TEST] opt5_4 map() :Optional[Optional[TEST]] opt5_4 flatMap() :Optional[TEST] ``` > map() 與 flatMap() 在於一個回傳的是物件原本的值,一個回傳的是將其檢整為同一層的值 > 實際上的處理法可以多與 Stream 方面搭配使用~ ## 結語 :::danger 以上就是簡單介紹關於 Optional 類別的使用方法,希望各位在閱讀文章後能夠對於 Null 的處理上提供幫助。 下一期的內容開始想進入到 MessageQueue,還在規畫要學哪一些東西,可能會耗費一段時間才更新了~ [六角鼠年鐵人賽 Week 19 - Spring Boot - Redis 介紹](/RoCdtpUVTrKOEy2dygR5RQ) ::: 首頁 [Kai 個人技術 Hackmd](/2G-RoB0QTrKzkftH2uLueA) ###### tags: `Spring Boot`,`w3HexSchool`