Jdk14都要出了,還不能使用 Optional優雅的處理空指針?
- 2019 年 11 月 5 日
- 筆記
1. 前言
如果你沒有處理過空指針,那麼你不是一位真正的 Java 程序員。
空指針確實會產生很多問題,我們經常遇到空的引用,然後又想從這個空的引用上去獲取其他的值,接着理所當然的碰到了 NullPointException
。這是你可能會想,這報錯很好處理,然後你看了眼報錯行數,對比了下代碼。腦海里瞬間閃過 」對對對,這裡有可能為空「,然後加上 null check
輕鬆處理。然而你不知道這已經是你處理的第多少個空指針異常了。
為了解決上面的問題,在 Java SE8 中引入了一個新類 java.util.Optional
,這個類可以緩解上面的問題。
你可能已經發現了,上面我用的是緩解而不是解決。這也是很多人理解不太對的地方,以為 Java SE8 中的 Optional
類可以解決空指針問題。其實 Optional 類的的使用只是提示你這裡可能存在空值,需要特殊處理,並提供了一些特殊處理的方法。如果你把 Optional
類當作空指針的救命稻草而不加思考的使用,那麼依舊會碰到錯誤。
因為 Optional
是的 Java SE8 中引入的,因此本文中難免會有一些 JDK8 中的語法,如 Lambda 表達式,流處理等,但是都是基本形式,不會有過於複雜的案例。
2. Optional 創建
Optional 的創建一共有三種方式。
/** * 創建一個 Optional */ @Test public void createOptionalTest() { // Optional 構造方式1 - of 傳入的值不能為 null Optional<String> helloOption = Optional.of("hello"); // Optional 構造方式2 - empty 一個空 optional Optional<String> emptyOptional = Optional.empty(); // Optional 構造方式3 - ofNullable 支持傳入 null 值的 optional Optional<String> nullOptional = Optional.ofNullable(null); }
其中構造方式1中 of
方法,如果傳入的值會空,會報出 NullPointerException
異常。
3. Optional 判斷
Optional 只是一個包裝對象,想要判斷裏面有沒有值可以使用 isPresent
方法檢查其中是否有值 。
/** * 檢查是否有值 */ @Test public void checkOptionalTest() { Optional<String> helloOptional = Optional.of("Hello"); System.out.println(helloOptional.isPresent()); Optional<Object> emptyOptional = Optional.empty(); System.out.println(emptyOptional.isPresent()); }
得到的輸出:
true false
從 JDK11 開始,提供了 isEmpty
方法用來檢查相反的結果:是否為空。
如果想要在有值的時候進行一下操作。可以使用 ifPresent
方法。
/** * 如果有值,輸出長度 */ @Test public void whenIsPresent() { // 如果沒有值,獲取默認值 Optional<String> helloOptional = Optional.of("Hello"); Optional<String> emptyOptional = Optional.empty(); helloOptional.ifPresent(s -> System.out.println(s.length())); emptyOptional.ifPresent(s -> System.out.println(s.length())); }
輸出結果:
5
4. Optional 獲取值
使用 get
方法可以獲取值,但是如果值不存在,會拋出 NoSuchElementException
異常。
/** * 如果沒有值,會拋異常 */ @Test public void getTest() { Optional<String> stringOptional = Optional.of("hello"); System.out.println(stringOptional.get()); // 如果沒有值,會拋異常 Optional<String> emptyOptional = Optional.empty(); System.out.println(emptyOptional.get()); }
得到結果:
hello java.util.NoSuchElementException: No value present at java.util.Optional.get(Optional.java:135) at net.codingme.feature.jdk8.Jdk8Optional.getTest(Jdk8Optional.java:91)
5. Optional 默認值
使用 orElse
, orElseGet
方法可以在沒有值的情況下獲取給定的默認值。
/** * 如果沒有值,獲取默認值 */ @Test public void whenIsNullGetTest() { // 如果沒有值,獲取默認值 Optional<String> emptyOptional = Optional.empty(); String orElse = emptyOptional.orElse("orElse default"); String orElseGet = emptyOptional.orElseGet(() -> "orElseGet default"); System.out.println(orElse); System.out.println(orElseGet); }
得到的結果:
orElse default orElseGet default
看到這裡你可能會有些疑惑了,這兩個方法看起來效果是一模一樣的,為什麼會提供兩個呢?下面再看一個例子,你會發現兩者的區別。
/** * orElse 和 orElseGet 的區別 */ @Test public void orElseAndOrElseGetTest() { // 如果沒有值,默認值 Optional<String> emptyOptional = Optional.empty(); System.out.println("空Optional.orElse"); String orElse = emptyOptional.orElse(getDefault()); System.out.println("空Optional.orElseGet"); String orElseGet = emptyOptional.orElseGet(() -> getDefault()); System.out.println("空Optional.orElse結果:"+orElse); System.out.println("空Optional.orElseGet結果:"+orElseGet); System.out.println("--------------------------------"); // 如果沒有值,默認值 Optional<String> stringOptional = Optional.of("hello"); System.out.println("有值Optional.orElse"); orElse = stringOptional.orElse(getDefault()); System.out.println("有值Optional.orElseGet"); orElseGet = stringOptional.orElseGet(() -> getDefault()); System.out.println("有值Optional.orElse結果:"+orElse); System.out.println("有值Optional.orElseGet結果:"+orElseGet); } public String getDefault() { System.out.println(" 獲取默認值中..run getDeafult method"); return "hello"; }
得到的輸出:
空Optional.orElse 獲取默認值中..run getDeafult method 空Optional.orElseGet 獲取默認值中..run getDeafult method 空Optional.orElse結果:hello 空Optional.orElseGet結果:hello -------------------------------- 有值Optional.orElse 獲取默認值中..run getDeafult method 有值Optional.orElseGet 有值Optional.orElse結果:hello 有值Optional.orElseGet結果:hello
在這個例子中會發現 orElseGet
傳入的方法在有值的情況下並不會運行。而 orElse
卻都會運行。
6. Optional 異常
使用 orElseThrow
在沒有值的時候拋出異常
/** * 如果沒有值,拋出異常 */ @Test public void whenIsNullThrowExceTest() throws Exception { // 如果沒有值,拋出異常 Optional<String> emptyOptional = Optional.empty(); String value = emptyOptional.orElseThrow(() -> new Exception("發現空值")); System.out.println(value); }
得到結果:
java.lang.Exception: 發現空值 at net.codingme.feature.jdk8.Jdk8Optional.lambda$whenIsNullThrowExceTest$7(Jdk8Optional.java:118) at java.util.Optional.orElseThrow(Optional.java:290) at net.codingme.feature.jdk8.Jdk8Optional.whenIsNullThrowExceTest(Jdk8Optional.java:118)
7. Optional 函數接口
Optional
隨 JDK8 一同出現,必然會有一些 JDK8 中的新特性,比如函數接口。Optional
中主要有三個傳入函數接口的方法,分別是filter
,map
,flatMap
。這裏面的實現其實是 JDK8 的另一個新特性了,因此這裡只是簡單演示,不做解釋。後面放到其他 JDK8 新特性文章里介紹。
@Test public void functionTest() { // filter 過濾 Optional<Integer> optional123 = Optional.of(123); optional123.filter(num -> num == 123).ifPresent(num -> System.out.println(num)); Optional<Integer> optional456 = Optional.of(456); optional456.filter(num -> num == 123).ifPresent(num -> System.out.println(num)); // map 轉換 Optional<Integer> optional789 = Optional.of(789); optional789.map(String::valueOf).map(String::length).ifPresent(length -> System.out.println(length)); }
得到結果:
123 3
8. Optional 案例
假設有計算機、聲卡、usb 三種硬件(下面的代碼中使用了 Lombok
的 @Data
註解)。
/** * 計算機 */ @Data class Computer { private Optional<SoundCard> soundCard; } /** * 聲卡 */ @Data class SoundCard { private Optional<Usb> usb; } /** * USB */ @Data class Usb { private String version; }
計算機可能會有聲卡,聲卡可能會有 usb。那麼怎麼取得 usb 版本呢?
/** * 電腦里【有可能】有聲卡 * 聲卡【有可能】有USB接口 */ @Test public void optionalTest() { // 沒有聲卡,沒有 Usb 的電腦 Computer computerNoUsb = new Computer(); computerNoUsb.setSoundCard(Optional.empty()); // 獲取 usb 版本 Optional<Computer> computerOptional = Optional.ofNullable(computerNoUsb); String version = computerOptional.flatMap(Computer::getSoundCard).flatMap(SoundCard::getUsb) .map(Usb::getVersion).orElse("UNKNOWN"); System.out.println(version); System.out.println("-----------------"); // 如果有值,則輸出 SoundCard soundCard = new SoundCard(); Usb usb = new Usb(); usb.setVersion("2.0"); soundCard.setUsb(Optional.ofNullable(usb)); Optional<SoundCard> optionalSoundCard = Optional.ofNullable(soundCard); optionalSoundCard.ifPresent(System.out::println); // 如果有值,則輸出 if (optionalSoundCard.isPresent()) { System.out.println(optionalSoundCard.get()); } // 輸出沒有值,則沒有輸出 Optional<SoundCard> optionalSoundCardEmpty = Optional.ofNullable(null); optionalSoundCardEmpty.ifPresent(System.out::println); System.out.println("-----------------"); // 篩選 Usb2.0 optionalSoundCard.map(SoundCard::getUsb) .filter(usb1 -> "3.0".equals(usb1.map(Usb::getVersion) .orElse("UBKNOW"))) .ifPresent(System.out::println); }
得到結果:
UNKNOWN ----------------- SoundCard(usb=Optional[Usb(version=2.0)]) SoundCard(usb=Optional[Usb(version=2.0)]) -----------------
9. Optional 總結
在本文中,我們看到了如何使用 Java SE8 的 java.util.Optional
類。Optional
類的目的不是為了替換代碼中的每個空引用,而是為了幫助更好的設計程序,讓使用者可以僅通過觀察屬性類型就可以知道會不會有空值。另外,Optional
不提供直接獲取值的方法,使用時會強迫你處理不存在的情況。間接的讓你的程序免受空指針的影響。
文中代碼已經上傳 Github。
https://github.com/niumoo/jdk-feature
JDK8 新特性系列文章:
Jdk14 都要出了,Jdk8 的時間處理姿勢還不了解一下?
<完>
我的微信:wn8398
個人主頁:https://www.codingme.net
本篇文章是博主原創文章,歡迎轉載,轉載時在明顯位置註明原文鏈接即可。
關注公眾號回復資源可以獲取Java 核心知識整理&面試資料。