一、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)