java8(六)用 Optional 取代 null

一、null帶來了哪些問題?

1)NullPointerException 是目前Java程序開發(fā)中最典型的異常。

2)它會(huì)使你的代碼膨脹。它讓你的代碼充斥著深度嵌套的 null 檢查。

3)它自身是毫無意義的。

4)它破壞了Java的哲學(xué)。Java一直試圖避免讓程序員意識(shí)到指針的存在,唯一的例外是: null 指針。

5)它在Java的類型系統(tǒng)上開了個(gè)口子。null 并不屬于任何類型,這意味著它可以被賦值給任意引用類型的變量。

簡(jiǎn)單舉例,有一輛汽車car,汽車有沒有上保險(xiǎn)呢?我們通過車的對(duì)象獲取一下,看看保險(xiǎn)的名字,代碼如下

import lombok.Data;

/**
 * @description: TestOptional
 * @author:weirx
 * @date:2021/10/26 14:26
 * @version:3.0
 */
public class TestOptional {

    @Data
    static class Car{

        private Insurance insurance;

        private String name;
    }

    @Data
    static class Insurance{

        private String name;
    }
    
    public static void main(String[] args) {
        Car car = new Car();
        car.getInsurance().getName();
    }
}

結(jié)果:

Exception in thread "main" java.lang.NullPointerException
    at com.cloud.bssp.java8.Optional.TestOptional.main(TestOptional.java:31)

如上所示,報(bào)了我們深惡痛絕的空指針異常,怎么防止呢?無非就是增加非空判斷:

car.getInsurance() == null ? null : car.getInsurance().getName();

雖然上面的代碼解決了空指針的問題,但是無形中增加了代碼的復(fù)雜程度,可讀性。

二、Optional 類入門

汲取 Haskell 和 Scala 的靈感,Java 8中引入了一個(gè)新的類 java.util.Optional<T> 。這是一個(gè)封裝 Optional 值的類。

如上面的空指針例子中,如果不知道車到底有沒有保險(xiǎn),就不應(yīng)該將保險(xiǎn)聲明為Insurance,而是應(yīng)該聲明為Optional<Insurance>。

當(dāng)變量存在時(shí), Optional 類只是對(duì)類簡(jiǎn)單封裝。變量不存在時(shí),缺失的值會(huì)被建模成一個(gè)“空”的 Optional 對(duì)象,由方法 Optional.empty() 返回。 Optional.empty() 方法是一個(gè)靜態(tài)工廠方法,它返回 Optional 類的特定單一實(shí)例。

使用Optional我們可以像下面這么聲明:

    static class Car {

        private Optional<Insurance> insuranceOptional;

        private String name;
    }

在語義上來講,使用Optional來聲明你的類,能非常清晰地界定出變量值的缺失是結(jié)構(gòu)上的問題,還是你算法上的缺陷,抑或是你數(shù)據(jù)中的問題。

三、Optional使用

3.1 創(chuàng)建Optional對(duì)象

        // 1、聲明一個(gè)空Optional
        Optional<Car> optional1 = Optional.empty();

        // 2、依據(jù)一個(gè)非空值創(chuàng)建Optional,如果傳入一個(gè)null,則會(huì)拋出空指針異常
        Optional<Car> optional2 = Optional.of(new Car());

        // 3、可接受null的Optional:使用靜態(tài)工廠方法 Optional.ofNullable ,你可以創(chuàng)建一個(gè)允許 null 值的 Optional
        // 如果 car 是 null ,那么得到的 Optional 對(duì)象就是個(gè)空對(duì)象。
        Optional<Car> optional3 = Optional.ofNullable(null);

3.2 使用 map 從 Optional 對(duì)象中提取和轉(zhuǎn)換值

Optional 提供了一個(gè) map 方法,使用方式如下:

        Optional<Car> optionalCar = Optional.ofNullable(car);
        Optional<String> optionalName = optionalCar.map(Car::getName);

與之前的文章當(dāng)中我們學(xué)習(xí)Stream中的map比較相像。

那么如果我們想要獲取保險(xiǎn)的名稱怎么獲取呢?

        Car car = new Car();
        car.setInsuranceOptional(Optional.empty());
        Optional<Car> optionalCar = Optional.ofNullable(car);
        optionalCar.map(Car::getInsuranceOptional).map(Insurance::getName);

如上這個(gè)代碼是無法通過編譯的。getInsuranceOptional返回的是Optional<Insurance>對(duì)象,接下來的map操作就成了對(duì)Optional<Optional<Insurance>>對(duì)象操作getName命令,這是違法的。

3.3 使用 flatMap 鏈接 Optional 對(duì)象

為了解決前面的問題,此時(shí)我們可以使用flatMap方法,與Stream中的flatMap有相同的效果。我們這里要做的是將兩層Optional合并成一個(gè)Optional:

        Car car = new Car();
        car.setInsuranceOptional(Optional.empty());
        Optional<Car> optionalCar = Optional.ofNullable(car);
        optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName);

如上所示我們給出的car中Insurance是空Optional,雖然運(yùn)行上面的代碼不會(huì)咋拋出空指針異常了,但是我們?nèi)孕枰獙?duì)其有個(gè)處理的話可以使用orElse(),如下所示:

        Car car = new Car();
        car.setInsuranceOptional(Optional.empty());
        Optional<Car> optionalCar = Optional.ofNullable(car);
        String unknown = optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).orElse("unknown");
        System.out.println(unknown);

-----------輸出---------------
unknown

3.4 讀取Optional對(duì)象中的值

3.4.1 get()

get() 是這些方法中最簡(jiǎn)單但又最不安全的方法。如果變量存在,它直接返回封裝的變量值,否則就拋出一個(gè)NoSuchElementException 異常。

除非你非常確定 Optional變量一定包含值,否則使用這個(gè)方法是個(gè)相當(dāng)糟糕的主意。

此外,這種方式即便相對(duì)于嵌套式的 null 檢查,也并未體現(xiàn)出多大的改進(jìn)。

        Car car = new Car();
        car.getInsuranceOptional().get();

--------輸出-----------
Exception in thread "main" java.util.NoSuchElementException: No value present
    at java.util.Optional.get(Optional.java:135)
    at com.cloud.bssp.java8.Optional.TestOptional.main(TestOptional.java:36)

3.4.2 orElse(T other)

orElse(T other)是在前面的例子中提到的方法。它允許你在Optional 對(duì)象不包含值時(shí)提供一個(gè)默認(rèn)值。

        Car car = new Car();
        car.setInsuranceOptional(Optional.empty());
        Optional<Car> optionalCar = Optional.ofNullable(car);
        String unknown = optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).orElse("unknown");
        System.out.println(unknown);

-----------輸出---------------
unknown

3.4.3 orElseGet(Supplier<? extends T> other)

orElse 方法的延遲調(diào)用版。

Supplier方法只有在 Optional 對(duì)象不含值時(shí)才執(zhí)行調(diào)用。

如果創(chuàng)建默認(rèn)值時(shí)需要執(zhí)行其他的方法做一些操作時(shí),或者你需要非常確定某個(gè)方法僅在Optional 為空時(shí)才進(jìn)行調(diào)用,也可以考慮該方式(這種情況有嚴(yán)格的限制條件)。

    public static String test() {
        System.out.println("this car has no insurance");
        return "unknown";
    }

        Car car = new Car();
        car.setInsuranceOptional(Optional.empty());
        Optional<Car> optionalCar = Optional.ofNullable(car);
        String unknown = optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).orElseGet(TestOptional::test);
        System.out.println(unknown);

-------輸出--------
this car has no insurance
unknown

3.4.4 orElseThrow(Supplier<? extends X> exceptionSupplier)

和 orElseGet方法非常類似,它們?cè)庥?Optional 對(duì)象為空時(shí)都會(huì)拋出一個(gè)異常,但是使用 orElseThrow 你可以定制希望拋出的異常類型。

    static class MyException extends RuntimeException{

    }

        Car car = new Car();
        car.setInsuranceOptional(Optional.empty());
        Optional<Car> optionalCar = Optional.ofNullable(car);
        String unknown = optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).orElseThrow(MyException::new);
        System.out.println(unknown);

-------輸出-----------
Exception in thread "main" com.cloud.bssp.java8.Optional.TestOptional$MyException
    at java.util.Optional.orElseThrow(Optional.java:290)
    at com.cloud.bssp.java8.Optional.TestOptional.main(TestOptional.java:42)

3.4.5 ifPresent(Consumer<? super T>)

能在變量值存在時(shí)執(zhí)行一個(gè)作為參數(shù)傳入的方法,否則就不進(jìn)行任何操作。

    static void addName(String name){
        System.out.println("the insurance is : " + name);
    }

        Car car = new Car();
        Insurance insurance = new Insurance();
        insurance.setName("車險(xiǎn)");
        car.setInsuranceOptional(Optional.of(insurance));
        Optional<Car> optionalCar = Optional.ofNullable(car);
        optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).ifPresent(TestOptional::addName);

--------------輸出--------------
the insurance is : 車險(xiǎn)

3.4.6 isPresent

如果存在就返回true,不存在就返回false。

        Car car = new Car();
        car.setInsuranceOptional(Optional.empty());
        System.out.println(car.getInsuranceOptional().isPresent());

        Insurance insurance = new Insurance();
        insurance.setName("車險(xiǎn)");
        car.setInsuranceOptional(Optional.of(insurance));
        System.out.println(car.getInsuranceOptional().isPresent());

--------------輸出--------------
false
true

3.4.7 filter(Predicate<? super T> predicate)

filter 方法接受一個(gè)謂詞作為參數(shù)。如果 Optional 對(duì)象的值存在,并且它符合謂詞的條件,filter 方法就返回其值;否則它就返回一個(gè)空的 Optional 對(duì)象。

        Car car = new Car();
        Insurance insurance = new Insurance();
        insurance.setName("車險(xiǎn)");
        car.setInsuranceOptional(Optional.of(insurance));
        Optional<Car> optionalCar = Optional.ofNullable(car);
        Optional<String> s = optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).filter("車險(xiǎn)"::equals);
        System.out.println(s.get());

------------輸出-----------
車險(xiǎn)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容