Jdk14都要出了,還不能使用 Optional優雅的處理空指針?

1. 前言

若是你沒有處理過空指針,那麼你不是一位真正的 Java 程序員。java


空指針確實會產生不少問題,咱們常常遇到空的引用,而後又想從這個空的引用上去獲取其餘的值,接着理所固然的碰到了 NullPointException。這是你可能會想,這報錯很好處理,而後你看了眼報錯行數,對比了下代碼。腦海裏瞬間閃過 」對對對,這裏有可能爲空「,而後加上 null check輕鬆處理。然而你不知道這已是你處理的第多少個空指針異常了。

爲了解決上面的問題,在 Java SE8 中引入了一個新類 java.util.Optional,這個類能夠緩解上面的問題。git

你可能已經發現了,上面我用的是緩解而不是解決。這也是不少人理解不太對的地方,覺得 Java SE8 中的 Optional 類能夠解決空指針問題。其實 Optional 類的的使用只是提示你這裏可能存在空值,須要特殊處理,並提供了一些特殊處理的方法。若是你把 Optional 類看成空指針的救命稻草而不加思考的使用,那麼依舊會碰到錯誤。程序員

由於 Optional 是的 Java SE8 中引入的,所以本文中不免會有一些 JDK8 中的語法,如 Lambda 表達式,流處理等,可是都是基本形式,不會有過於複雜的案例。github

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());
}
複製代碼

獲得的輸出:spa

true
false
複製代碼

從 JDK11 開始,提供了 isEmpty方法用來檢查相反的結果:是否爲空。.net

若是想要在有值的時候進行一下操做。可使用 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 中主要有三個傳入函數接口的方法,分別是filtermapflatMap。這裏面的實現實際上是 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

<完>

我的網站:www.codingme.net
若是你喜歡這篇文章,能夠關注公衆號,一塊兒成長。
關注公衆號回覆資源能夠沒有套路的獲取全網最火的的 Java 核心知識整理&面試資料。

公衆號
相關文章
相關標籤/搜索