一、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());
------------輸出-----------
車險