1. 程式人生 > >Java8新特性整理之Optional取代Null引用

Java8新特性整理之Optional取代Null引用

Java8新特性整理之Optional取代Null引用

 

版權宣告:歡迎轉載!請註明出處! https://blog.csdn.net/u011726984/article/details/79315837

java8之前如何避免空指標異常

相信大家在開發中都會碰到NullPointerException 空指標異常導致程式停止的情況。

下面就來談談在java8之前如何避免空指標異常。

一個擁有汽車及汽車保險的客戶。

Person.java

public class Person { 
    private Car car; 
    public Car getCar() { return car; } 
} 

Car.java

public class Car { 
    private Insurance insurance; 
    public Insurance getInsurance() { return insurance; } 
} 

Insurance.java

public class Insurance { 
    private String name; 
    public String getName() { return name; } 
} 

現在我們需要獲取客戶的汽車投保的公司名稱,java8之前我們一般這麼做:

private String getCarInsuranceName(Person person) {
        if (person != null) {
            Car car = person.getCar();
            if (car != null) {
                Insurance insurance = car.getInsurance();
                if (insurance != null) {
                    return insurance.getName();

                }
            }
        }
        return "Unknown";
    }

這是糟糕的,不易閱讀的程式碼。每個null檢查都會增加呼叫鏈上剩餘程式碼的巢狀層數。

或者:

 private String getCarInsuranceName2(Person person) {
        if (person == null) {
            return "Unknown";
        }
        Car car = person.getCar();
        if (car == null) {
            return "Unknown";
        }
        Insurance insurance = car.getInsurance();
        if (insurance == null) {
            return "Unknown";
        }
        return insurance.getName();
    }

這會導致每個null檢查都會新增新的退出點。

java8之後如何避免空指標異常

那麼java8中如何優雅的解決上面的問題呢?這就要引入Optional類。

變數存在時, Optional類只是對類簡單封裝。變數不存在時,缺失的值會被建模成一個“空”的Optional物件,由方法Optional.empty()返回。

null引用和Optional.empty()有什麼本質的區別嗎?

從語義上,你可以把它們當作一回事兒,但是實際中它們之間的差別非常大:如果你嘗試解引用一個null,一定會觸發NullPointerException,不過使用Optional.empty()就完全沒事兒,它是Optional類的一個有效物件,多種場景都能呼叫,非常有用。

使用Optional重新定義Person/Car/Insurance的資料模型
Person.java

public class Person { 
    private Optional<Car> car;
    public Optional<Car> getCar() { return car; }
} 

Car.java

public class Car { 
    private Optional<Insurance> insurance;
    public Optional<Insurance> getInsurance() { return insurance; }
} 

Insurance.java

public class Insurance { 
    private String name; 
    public String getName() { return name; } 
} 

現在我們需要獲取客戶的汽車投保的公司名稱,java8之前我們一般這麼做:

 public String getCarInsuranceAsOptional(Optional<Person> person) {
        return person.flatMap(Person::getCar)
                .flatMap(Car::getInsurance)
                .map(Insurance::getName)
                .orElse("UnKnown");
    }

解釋下,Optional封裝的Person物件,建立 Optional 物件 有三種方式:
1. 宣告一個空的Optional

Optional<Person> optPerson = Optional.empty(); 
  1. 依據一個非空值建立Optional
Optional<Person> optPerson = Optional.of(person); 
  1. 可接受null的Optional
Optional<Person> optPerson = Optional.ofNullable(person); 

如果car是null,那麼得到的Optional物件就是個空物件。

使用 flatMapmap從 Optional 物件中提取和轉換值
optPerson是Optional型別的變數, 呼叫map方法應該沒有問題。但getCar返回的是一個Optional型別的物件,這意味著map操作的結果是一個Optional

public class Person { 
    private Car car; 
    public Optional<Car> getCarAsOptional() { 
        return Optional.ofNullable(car); 
    } 
} 

Optional類提供了多種方法讀取Optional例項中的變數值
- get()是這些方法中最簡單但又最不安全的方法。如果變數存在,它直接返回封裝的變數值,否則就丟擲一個NoSuchElementException異常。所以,除非你非常確定Optional
變數一定包含值,否則使用這個方法是個相當糟糕的主意。此外,這種方式即便相對於巢狀式的null檢查,也並未體現出多大的改進。
- orElse(T other),它允許你在Optional物件不包含值時提供一個預設值。
- orElseGet(Supplier<? extends T> other)是orElse方法的延遲呼叫版, Supplier
方法只有在Optional物件不含值時才執行呼叫。如果建立預設值是件耗時費力的工作,你應該考慮採用這種方式(藉此提升程式的效能),或者你需要非常確定某個方法僅在
Optional為空時才進行呼叫,也可以考慮該方式(這種情況有嚴格的限制條件)。
- orElseThrow(Supplier<? extends X> exceptionSupplier)和get方法非常類似,
它們遭遇Optional物件為空時都會丟擲一個異常,但是使用orElseThrow你可以定製希望丟擲的異常型別。
- ifPresent(Consumer<? super T>)讓你能在變數值存在時執行一個作為引數傳入的方法,否則就不進行任何操作。

使用 Optional的filter方法過濾特定條件

需求:你可能需要檢查保險公司的名稱是否為“香港保險”

java8之前需要這麼做:

public void testGetInsuranceName(Car car) {
        Insurance insurance = car.getInsurance();
        if(insurance != null && "香港保險".equals(insurance.getName())) {
            System.out.println("ok");
        }
    }

java8之後這麼做:

public void testGetInsuranceName2(Car car) {
        Optional<Insurance> optInsurance = car.getInsurance();
        optInsurance.filter(insurance -> "香港保險".equals(insurance.getName()))
                .ifPresent(x -> System.out.println("ok"));
    }

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

基礎型別的Optional物件,以及為什麼應該避免使用它們

不知道你注意到了沒有,與Stream物件一樣,Optional也提供了類似的基礎型別——OptionalIntOptionalLong以及OptionalDouble

之前討論過使用基礎型別Stream的場景,尤其是如果Stream物件包含了大量元素,出於效能的考量,
使用基礎型別是不錯的選擇,但對Optional物件而言,這個理由就不成立了,因為Optional物件最多隻包含一個值

小結

  • null引用在歷史上被引入到程式設計語言中,目的是為了表示變數值的缺失。
  • Java 8中引入了一個新的類java.util.Optional,對存在或缺失的變數值進行建模。
  • 你可以使用靜態工廠方法Optional.empty、Optional.of以及Optional.ofNullable建立Optional物件。
  • Optional類支援多種方法,比如map、flatMap、filter,它們在概念上與Stream類中對應的方法十分相似。
  • 使用Optional會迫使你更積極地解引用Optional物件,以應對變數值缺失的問題,最終,你能更有效地防止程式碼中出現不期而至的空指標異常。
  • 使用Optional能幫助你設計更好的API,使用者只需要閱讀方法簽名,就能瞭解該方法是否接受一個Optional型別的值。