java8(六)用 Optional 取代 null 一、null帶來了哪些問題? 二、Optional 類入門 三、Optional使用

一、null帶來了哪些問題?

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

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

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

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

5)它在Java的類型系統上開了個口子。null 並不屬於任何類型,這意味着它可以被賦值給任意引用類型的變量。

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

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();
    }
}

結果:

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

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

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

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

二、Optional 類入門

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

如上面的空指針例子中,如果不知道車到底有沒有保險,就不應該將保險聲明爲Insurance,而是應該聲明爲Optional<Insurance>。

當變量存在時, Optional 類只是對類簡單封裝。變量不存在時,缺失的值會被建模成一個“空”的 Optional 對象,由方法 Optional.empty() 返回。 Optional.empty() 方法是一個靜態工廠方法,它返回 Optional 類的特定單一實例。

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

    static class Car {

        private Optional<Insurance> insuranceOptional;

        private String name;
    }

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

三、Optional使用

3.1 創建Optional對象

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

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

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

3.2 使用 map 從 Optional 對象中提取和轉換值

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

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

與之前的文章當中我們學習Stream中的map比較相像。

那麼如果我們想要獲取保險的名稱怎麼獲取呢?

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

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

3.3 使用 flatMap 鏈接 Optional 對象

爲了解決前面的問題,此時我們可以使用flatMap方法,與Stream中的flatMap有相同的效果。我們這裏要做的是將兩層Optional合併成一個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,雖然運行上面的代碼不會咋拋出空指針異常了,但是我們仍需要對其有個處理的話可以使用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對象中的值

3.4.1 get()

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

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

此外,這種方式即便相對於嵌套式的 null 檢查,也並未體現出多大的改進。

        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 對象不包含值時提供一個默認值。

        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 方法的延遲調用版。

Supplier方法只有在 Optional 對象不含值時才執行調用。

如果創建默認值時需要執行其他的方法做一些操作時,或者你需要非常確定某個方法僅在Optional 爲空時才進行調用,也可以考慮該方式(這種情況有嚴格的限制條件)。

    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方法非常類似,它們遭遇 Optional 對象爲空時都會拋出一個異常,但是使用 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>)

能在變量值存在時執行一個作爲參數傳入的方法,否則就不進行任何操作。

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

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

--------------輸出--------------
the insurance is : 車險

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("車險");
        car.setInsuranceOptional(Optional.of(insurance));
        System.out.println(car.getInsuranceOptional().isPresent());

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

3.4.7 filter(Predicate<? super T> predicate)

filter 方法接受一個謂詞作爲參數。如果 Optional 對象的值存在,並且它符合謂詞的條件,filter 方法就返回其值;否則它就返回一個空的 Optional 對象。

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

------------輸出-----------
車險
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章