系統環境:
- Java JDK 版本:1.8
參考地址:
- Oracle JDK API 參考文檔
https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
- 菜鳥教程-Java 8 Optional 類
https://www.runoob.com/java/java8-optional-class.html
1.Optional簡介
Optional 是一個容器對象,可以存儲對象、字符串等值,當然也可以存儲 null 值。Optional 提供很多有用的方法,能幫助我們將 Java 中的對象等一些值存入其中,這樣我們就不用顯式進行空值檢測,使我們能夠用少量的代碼完成復雜的流程。
比如它提供了:
-
of() 方法,可以將值存入 Optional 容器中,如果存入的值是 null 則拋異常。
-
ofNullable() 方法,可以將值存入 Optional 容器中,即使值是 null 也不會拋異常。
-
get() 方法,可以獲取容器中的值,如果值為 null 則拋出異常。
-
getElse() 方法,可以獲取容器中的值,如果值為 null 則返回設置的默認值。
-
isPresent() 方法,該方法可以判斷存入的值是否為空。
-
…等等一些其它常用方法,下面會進行介紹。
可以說,使用 Optional 可以幫助我們解決業務中,減少值動不動就拋出空指針異常問題,也減少 null 值的判斷,提高代碼可讀性等,這里我們介紹下,如果使用這個 Optional 類。
2.Optional類描述
-
Optional 類所在包: java.util.Optional
-
Optional 類聲明: public final class Optional extends Object
-
Optional 類方法: public Optional filter(Predicate<? super T> predicate)
簡單來說,Opitonal類就是Java提供的為了解決大家平時判斷對象是否為空用會用 null!=obj 這樣的方式存在的判斷,從而令人頭疼導致NPE(Null Pointer Exception 空指針異常),同時Optional的存在可以讓代碼更加簡單,可讀性跟高,代碼寫起來更高效.
常規判斷:
//對象 人 //屬性有 name,age Person person=new Person(); if (null==person){ return "person為null"; } return person;
使用Optional:
//對象 人 //屬性有 name,age Person person=new Person(); return Optional.ofNullable(person).orElse("person為null");
3、Optional 常用方法及使用示例
1、靜態方法 Optional.of()
-
方法作用: 為指定的值創建一個指定非 null 值的 Optional。
-
方法描述: of 方法通過工廠方法創建 Optional 實例,需要注意的是傳入的參數不能為 null,否則拋出 NullPointerException。
-
返回類型: Optional
-
示例代碼:
調用兩個 Optional.of() 方法,一個傳入正常參數,另一個傳入 null 參數:
public static void main(String[] args) { // 傳入正常值,正常返回一個 Optional 對象 Optional<String> optional1 = Optional.of("mydlq"); // 傳入參數為 null,拋出 NullPointerException. Optional optional2 = Optional.of(null); }
運行代碼,可以觀察到控制台輸出內容如下:
Exception in thread "main" java.lang.NullPointerException at java.util.Objects.requireNonNull(Objects.java:203) at java.util.Optional.<init>(Optional.java:96) at java.util.Optional.of(Optional.java:108) at club.mydlq.OptionalExample.main(OptionalExample.java:12)
可以看到傳入正常參數正常返回 Optional 對象,傳入 null 參數返回 NullPointerException 異常。
2、靜態方法 Optional.ofNullable()
-
方法作用: 為指定的值創建一個 Optional 對象,如果指定的參數為 null,不拋出異常,直接則返回一個空的 Optional 對象。
-
方法描述: ofNullable 方法是和 of 方式一樣,都是用於創建 Optional 對象,只是傳入的參數 null 時,會返回一個空的 Optional 對象,而不會拋出 NullPointerException 異常。
-
返回類型: Optional
-
示例代碼:
調用 Optional.ofNullable() 方法,傳入 null 參數:
public static void main(String[] args) { // 傳入正常值,正常返回一個 Optional 對象 Optional<String> optional1 = Optional.ofNullable("mydlq"); // 傳入 null 參數,正常返回 Optional 對象 Optional optional2 = Optional.ofNullable(null); }
運行代碼,可以觀察到正常傳入值和傳入 null 值時,都沒有拋出異常。
3、對象方法 isPresent()
-
方法作用: 如果值存在則方法會返回 true,否則返回 false。
-
方法描述: 該方法其實就是用於判斷創建 Optional 時傳入參數的值是否為空,實現代碼就簡單一行,即
value != null
所以如果不為空則返回 true,否則返回 false。 -
返回類型: boolean
-
示例代碼:
public static void main(String[] args) { // 傳入正常值,正常返回一個 Optional 對象,並使用 isPresent 方法 Optional optional1 = Optional.ofNullable("mydlq"); System.out.println("傳入正常值返回:" + optional1.isPresent()); // 傳入參數為 null 生成一個 Optional 對象,並使用 isPresent 方法 Optional optional2 = Optional.ofNullable(null); System.out.println("傳入 null 值返回:" + optional2.isPresent()); }
運行代碼,可以觀察到控制台輸出內容如下:
傳入正常值返回:true 傳入 null 值返回:false
可以看到傳入正常參數時調用 Optional 對象的 isPresent 方法時返回 true,傳入 null 參數返回 false。
4、對象方法 get()
-
方法作用: 如果 Optional 有值則將其返回,否則拋出 NoSuchElementException 異常。
-
方法描述: get 方法內部實現其實就是判斷 Otpional 對象中的 value 屬性是否為 null,如果是就拋出 NoSuchElementException 異常,否則返回這個 value 值。
-
返回類型: T
-
示例代碼:
public static void main(String[] args) { // 傳入正常值,正常返回一個 Optional 對象,並使用 get 方法獲取值 Optional optional1 = Optional.ofNullable("mydlq"); System.out.println(optional1.get()); // 傳入參數為 null 生成一個 Optional 對象,並使用 get 方法獲取值 Optional optional2 = Optional.ofNullable(null); System.out.println(optional2.get()); }
運行代碼,可以觀察到控制台輸出內容如下:
傳入正常參數:mydlq Exception in thread "main" java.util.NoSuchElementException: No value present at java.util.Optional.get(Optional.java:135) at club.mydlq.OptionalExample.main(OptionalExample.java:14)
可以觀察到傳入正常值的 Optional 調用 get 方法正常輸出值,通過空的 optional 對象使用 get 方法獲取值時,拋出 NoSuchElementException 異常:
5、對象方法 ifPresent()
-
方法作用: 如果值存在則使用該值調用 consumer , 否則不做任何事情。
-
方法描述: 該方法 ifPresent(Consumer<? super T> consumer) 中參數接收的是 Consumer 類,它包含一個接口方法 accept(),該方法能夠對傳入的值進行處理,但不會返回結果。這里傳入參數可以傳入 Lamdda 表達式或 Consumer 對象及實現 Consumer 接口的類的對象。
-
返回類型: void
-
示例代碼:
public static void main(String[] args) { // 創建 Optional 對象,然后調用 Optional 對象的 ifPresent 方法,傳入 Lambda 表達式 Optional optional1 = Optional.ofNullable("mydlq1"); optional1.ifPresent((value) -> System.out.println("Optional 的值為:" + value)); // 創建 Optional 對象,調用 Optional 對象的 ifPresent 方法,傳入實現 Consumer 匿名內部類 Optional optional2 = Optional.ofNullable("mydlq2"); Consumer<String> consumer = new Consumer() { @Override public void accept(Object value) { System.out.println("Optional 的值為:" + value); } }; optional2.ifPresent(consumer); }
運行代碼,可以觀察到控制台輸出內容如下:
Optional 的值為:mydlq1 Optional 的值為:mydlq2
可以觀察到,調用 ifPresent 使用 lambda 或者內部匿名類方法,都是為了再執行 Optional 對象的 ifPresent 方法時,執行一段代碼邏輯。
6、對象方法 orElse()
-
方法作用: 如果該值存在就直接返回, 否則返回指定的其它值。
-
方法描述: orElse 方法實現很簡單,就是使用三目表達式對傳入的參數值進行 null 驗證,即
value != null ? value : other;
如果為 null 則返回 true,否則返回 false。 -
返回類型: T
-
示例代碼:
public static void main(String[] args) { // 傳入正常參數,獲取一個 Optional 對象,並使用 orElse 方法設置默認值 Optional optional1 = Optional.ofNullable("mydlq"); Object object1 = optional1.orElse("默認值"); System.out.println("如果值不為空:"+object1); // 傳入 null 參數,獲取一個 Optional 對象,並使用 orElse 方法設置默認值 Optional optional2 = Optional.ofNullable(null); Object object2 = optional2.orElse("默認值"); System.out.println("如果值為空:"+object2); }
運行代碼,可以觀察到控制台輸出內容如下:
如果值不為空:mydlq 如果值為空:默認值
可以觀察到,如果 Optional 的值為空,則返回 orElse() 方法設置的默認值,否則返回 Optional 中的值。
7、對象方法 orElseGet()
-
方法作用: 如果該值存在就返回值,否則觸發 other,並返回 other 調用的結果。
-
方法描述: orElseGet 方法和 orElse 方法類似,都是在 Optional 值為空時,返回一個默認操作,只不過 orElse 返回的是默認值,而 orElseGet 是執行 lambda 表達式,然后返回 lambda 表達式執行后的結果。
-
返回類型: T
-
示例代碼:
public static void main(String[] args) { // 傳入正常參數,獲取一個 Optional 對象,並使用 orElse 方法設置默認值 Optional optional1 = Optional.ofNullable("mydlq"); Object object1 = optional1.orElseGet(() -> { String defaultVal = "執行邏輯和生成的默認值"; return defaultVal; }); System.out.println("輸出的值為:"+object1); // 傳入 null 參數,獲取一個 Optional 對象,並使用 orElse 方法設置默認值 Optional optional2 = Optional.ofNullable(null); Object object2 = optional2.orElseGet(() -> { String defaultVal = "執行邏輯和生成的默認值"; return defaultVal; }); System.out.println("輸出的值為:"+object2); }
運行代碼,可以觀察到控制台輸出內容如下:
輸出的值為:mydlq
輸出的值為:執行邏輯和生成的默認值
可也觀察到,當 Optional 值為不為空時正常返回帶值的 Optional,如果 Optional 為空則返回 orElseGet 方法中 lambda 表達式執行后生成的值。
8、對象方法 orElseThrow()
-
方法作用: 如果 Optional 存在該值,返回包含的值,否則拋出由 Supplier 繼承的異常。
-
方法描述: orElseThrow 方法其實就是判斷創建 Optional 時傳入的參數是否為 null,如果是非 null 則返回傳入的值,否則拋出 異常。
-
返回類型: T
-
示例代碼:
public static void main(String[] args) { // 傳入正常參數,獲取一個 Optional 對象,並使用 orElseThrow 方法 try { Optional optional1 = Optional.ofNullable("mydlq"); Object object1 = optional1.orElseThrow(() -> { System.out.println("執行邏輯,然后拋出異常"); return new RuntimeException("拋出異常"); } ); System.out.println("輸出的值為:" + object1); } catch (Throwable throwable) { throwable.printStackTrace(); } // 傳入 null 參數,獲取一個 Optional 對象,並使用 orElseThrow 方法 try { Optional optional2 = Optional.ofNullable(null); Object object2 = optional2.orElseThrow(() -> { System.out.println("執行邏輯,然后拋出異常"); return new RuntimeException("拋出異常"); } ); System.out.println("輸出的值為:" + object2); } catch (Throwable throwable) { throwable.printStackTrace(); } }
運行代碼,可以觀察到控制台輸出內容如下:
值為不為空輸出的值:mydlq 執行邏輯,然后拋出異常 java.lang.RuntimeException: 拋出異常 at club.mydlq.OptionalExample.lambda$main$1(OptionalExample.java:25) at java.util.Optional.orElseThrow(Optional.java:290) at club.mydlq.OptionalExample.main(OptionalExample.java:23)
可以觀察到,當創建 Optional 時如果傳入的參數為空則執行 Lambda 表達式代碼邏輯后拋出異常信息,否則返回傳入的參數值。
9、對象方法 map()
-
方法作用: 如果有值,則對其執行調用映射函數得到返回值。如果返回值不為 null,則創建包含映射返回值的 Optional 作為 map 方法返回值,否則返回空 Optional。
-
方法描述: map 方法主要用於獲取某個對象中的某個屬性值的 Optional 對象時使用。map 方法調用時,首先驗證傳入的映射函數是否為空,如果為空則拋出異常。然后,再檢測 Optional 的 value 是否為空,如果是,則返回一個空 value 的 Optional 對象。如果傳入的映射函數和 Optinal 的 value 都不為空,則返回一個帶 value 對象屬性的 Optional 對象。
-
返回類型: Optional<U>
-
示例代碼:
示例1: 創建 Map 集合,存儲一些鍵值對信息,通過 Optional 操作 Map 獲取值,然后觀察:
public static void main(String[] args) { // 創建 map 對象 Map<String, String> userMap = new HashMap<>(); userMap.put("name1", "mydlq"); userMap.put("name2", null); // 傳入 Map 對象參數,獲取一個 Optional 對象,獲取 name1 屬性 Optional<String> optional1 = Optional.of(userMap).map(value -> value.get("name1")); // 傳入 Map 對象參數,獲取一個 Optional 對象,獲取 name2 屬性 Optional<String> optional2 = Optional.of(userMap).map(value -> value.get("name2")); // 獲取 Optional 的值 System.out.println("獲取的 name1 的值:" + optional1.orElse("默認值")); System.out.println("獲取的 name2 的值:" + optional2.orElse("默認值")); }
運行代碼,可以觀察到控制台輸出內容如下:
獲取的 Optional 的值:mydlq
獲取的 Optional 的值:默認值
示例2: 創建一個用戶類,使用 Optional 操作用戶對象,獲取其 name 參數,結合 Optional 的 map 方法獲取值,進行觀察:
public class User { private String name; public User(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
使用 Optional 的 map 方法對值處理:
public static void main(String[] args) { // 創建一個對象,設置姓名屬性而不設置性別,這時候性別為 null User user1 = new User("測試名稱"); User user2 = new User(); // 使用 Optional 存儲 User 對象 Optional<User> optional1 = Optional.ofNullable(user1); Optional<User> optional2 = Optional.ofNullable(user2); // 獲取對象的 name 屬性值 String name1 = optional1.map(User::getName).orElse("未填寫"); String name2 = optional2.map(User::getName).orElse("未填寫"); // 輸出結果 System.out.println("獲取的名稱:" + name1); System.out.println("獲取的名稱:" + name2); }
運行代碼,可以觀察到控制台輸出內容如下:
獲取的名稱:測試名稱
獲取的名稱:未填寫
總結:
通過上面兩個示例觀察到,通過 Optional 對象的 map 方法能夠獲取映射對象中的屬,創建 Optional 對象,並以此屬性充當 Optional 的值,結合 orElse 方法,如果獲取的屬性的值為空,則設置個默認值。
10、對象方法 flatMap()
-
方法作用: 如果值存在,返回基於 Optional 包含的映射方法的值,否則返回一個空的 Optional。
-
方法描述: flatMap 方法和 map 方法類似,唯一的不同點就是 map 方法會對返回的值進行 Optional 封裝,而 flatMap 不會,它需要手動執行 Optional.of 或 Optional.ofNullable 方法對 Optional 值進行封裝。
-
返回類型: Optional<U>
-
示例代碼:
public static void main(String[] args) { // 創建 map 對象 Map<String, String> userMap = new HashMap<>(); userMap.put("name", "mydlq"); userMap.put("sex", "男"); // 傳入 Map 對象參數,獲取一個 Optional 對象 Optional<Map<String, String>> optional1 = Optional.of(userMap); // 使用 Optional 的 flatMap 方法,獲取 Map 中的 name 屬性 // 然后通過獲取的值手動創建一個新的 Optional 對象 Optional optional2 = optional1.flatMap(value -> Optional.ofNullable(value.get("name"))); // 獲取 Optional 的 value System.out.println("獲取的 Optional 的值:" + optional2.get()); }
運行代碼,可以觀察到控制台輸出內容如下:
獲取的 Optional 的值:mydlq
根據結果觀察,可以看到 flatMap 和 map 方法沒有什么區別,但是仔細看,代碼中調用 flatMap 后,需要手動執行 of 或 ofNullable 方法創建了 Optional 對象。
11、對象方法 filter()
-
方法作用: 如果有值並且滿足斷言條件返回包含該值的 Optional,否則返回空 Optional。
-
方法描述: filter 方法通過傳入的限定條件對 Optional 實例的值進行過濾,如果 Optional 值不為空且滿足限定條件就返回包含值的 Optional,否則返回空的 Optional。這里設置的限定條件需要使用實現了 Predicate 接口的 lambda 表達式來進行配置。
-
返回類型: Optional<T>
-
示例代碼:
public static void main(String[] args) { // 創建一個測試的 Optional 對象 Optional<String> optional = Optional.ofNullable("mydlq"); // 調用 Optional 的 filter 方法,設置一個滿足的條件,然后觀察獲取的 Optional 對象值是否為空 Optional optional1 =optional.filter((value) -> value.length() > 2); System.out.println("Optional 的值不為空::" + optional.isPresent()); // 調用 Optional 的 filter 方法,設置一個不滿足的條件,然后觀察獲取的 Optional 對象值是否為空 Optional optional2 =optional.filter((value) -> value.length() <2); System.out.println("Optional 的值不為空::" + optional2.isPresent()); }
運行代碼,可以觀察到控制台輸出內容如下:
Optional 的值不為空:true Optional 的值不為空:false
根據結果可以觀察到,可以通過 filter 設置一個條件來判斷 Optional 的值,如果滿足條件就返回帶值的 Optional,否則返回空的 Optional。
4、Optional 常用示例組合
在介紹一欄中已經說過 Optional 是個容器,它可用保存類型的 T 的值,即使 T 為 null 也可以使用 Optional 存儲,這樣我就不用顯示進行空值檢測,防止空指針異常。
上面也介紹了 Optional 的各種方法,在實際使用中這些方法常常組合使用。且很多方法也常與 Lambda 表達式結合,獲取我們想要的結果的值。
下面是常用的示例,可以作為參考:
對集合中的對象屬性進行過濾
創建一個 User 對象實體類,里面包含 name 屬性:
public class User { private String name; public User(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
創建一個使用 main 方法的類,創建幾個 User 對象且設置不同的值,有的對象為 null 有的屬性不設置,然后通過 Optional 獲取 name 屬性值加入集合,進行測試:
import java.util.ArrayList; import java.util.List; import java.util.Optional; public class OptionalExample { /** * 測試的 main 方法 */ public static void main(String[] args) { // 創建一個測試的用戶集合 List<User> userList = new ArrayList<>(); // 創建幾個測試用戶 User user1 = new User("abc"); User user2 = new User("efg"); User user3 = null; // 將用戶加入集合 userList.add(user1); userList.add(user2); userList.add(user3); // 創建用於存儲姓名的集合 List<String> nameList = new ArrayList(); // 循環用戶列表獲取用戶信息,值獲取不為空且用戶以 a 開頭的姓名, // 如果不符合條件就設置默認值,最后將符合條件的用戶姓名加入姓名集合 for (User user : userList) { nameList.add(Optional.ofNullable(user).map(User::getName).filter(value -> value.startsWith("a")).orElse("未填寫")); } // 輸出名字集合中的值 System.out.println("通過 Optional 過濾的集合輸出:"); nameList.stream().forEach(System.out::println); } }
輸出運行結果:
通過 Optional 過濾的集合輸出:
abc
未填寫
未填寫
通過上面,可以觀察到,使用 Optional 有時候可以很方便的過濾一些屬性,而且它的方法可以通過鏈式調用,方法間相互組合使用,使我們用少量的代碼就能完成復雜的邏輯。
5.相似方法進行對比分析
可能小伙伴看到這,沒用用過的話會覺得orElse()和orElseGet()還有orElseThrow()很相似,map()和flatMap()好相似,哈哈哈不用着急,都是從這一步過來的,我再給大家總結一下不同方法的異同點
orElse()和orElseGet()和orElseThrow()的異同點
方法效果類似,如果對象不為空,則返回對象,如果為空,則返回方法體中的對應參數,所以可以看出這三個方法體中參數是不一樣的
-
orElse(T 對象)
-
orElseGet(Supplier < T >對象)
-
orElseThrow(異常)
map()和orElseGet的異同點
-
方法效果類似,對方法參數進行二次包裝,並返回,入參不同
-
map(function函數)
-
flatmap(Optional< function >函數)
具體要怎么用,要根據業務場景以及代碼規范來定義,下面可以簡單看一下我在實戰中怎用使用神奇的Optional
實戰場景再現
場景1:
在service層中查詢一個對象,返回之后判斷是否為空並做處理
//查詢一個對象 Member member = memberService.selectByIdNo(request.getCertificateNo()); //使用ofNullable加orElseThrow做判斷和操作 Optional.ofNullable(member).orElseThrow(() -> new ServiceException("沒有查詢的相關數據"));
場景2:
我們可以在dao接口層中定義返回值時就加上Optional 例如:我使用的是jpa,其他也同理
public interface LocationRepository extends JpaRepository<Location, String> { Optional<Location> findLocationById(String id); }
然在是Service中
public TerminalVO findById(String id) { //這個方法在dao層也是用了Optional包裝了 Optional<Terminal> terminalOptional = terminalRepository.findById(id); //直接使用isPresent()判斷是否為空 if (terminalOptional.isPresent()) { //使用get()方法獲取對象值 Terminal terminal = terminalOptional.get(); //在實戰中,我們已經免去了用set去賦值的繁瑣,直接用BeanCopy去賦值 TerminalVO terminalVO = BeanCopyUtils.copyBean(terminal, TerminalVO.class); //調用dao層方法返回包裝后的對象 Optional<Location> location = locationRepository.findLocationById(terminal.getLocationId()); if (location.isPresent()) { terminalVO.setFullName(location.get().getFullName()); } return terminalVO; } //不要忘記拋出異常 throw new ServiceException("該終端不存在"); }
6.Optional使用注意事項
Optional真么好用,真的可以完全替代if判斷嗎?
我想這肯定是大家使用完之后Optional之后可能會產生的想法,答案是否定的,舉一個最簡單的栗子:
如果我只想判斷對象的某一個變量是否為空並且做出判斷呢?
Person person=new Person(); person.setName(""); persion.setAge(2); //普通判斷 if(StringUtils.isNotBlank(person.getName())){ //名稱不為空執行代碼塊 } //使用Optional做判斷 Optional.ofNullable(person).map(p -> p.getName()).orElse("name為空");
我覺得這個例子就能很好的說明這個問題,只是一個很簡單判斷,如果用了Optional我們還需要考慮包裝值,考慮代碼書寫,考慮方法調用,雖然只有一行,但是可讀性並不好,如果別的程序員去讀,我覺得肯定沒有if看的明顯。
7.jdk1.9對Optional優化
首先增加了三個方法:or()
、ifPresentOrElse()
和 stream()
- or() 與orElse等方法相似,如果對象不為空返回對象,如果為空則返回or()方法中預設的值。
- ifPresentOrElse() 方法有兩個參數:一個 Consumer 和一個 Runnable。如果對象不為空,會執行 Consumer 的動作,否則運行 Runnable。相比ifPresent()多了OrElse判斷。
- stream()將Optional轉換成stream,如果有值就返回包含值的stream,如果沒值,就返回空的stream。
因為這個jdk1.9的Optional具體我沒有測試,同時也發現有蠻好的文章已經也能讓大家明白jdk1.9的option的優化,我就不深入去說了。
原文參考公眾號【Java知音】