# 六角鼠年鐵人賽 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

如圖所示,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`