Java 中 NullPointerException 的完美解決方案
null在Java中帶來的麻煩
我相信所有的Java程序猿一定都遇到過 NullPointerException
,空指針在Java程序中是最常見的,也是最煩人的;它讓我們很多程序猿產(chǎn)生了根深蒂固的感覺,所有可能產(chǎn)生空指針的地方都得加上 if-else
檢查,但是這帶給我們很多麻煩。
-
Java本身是強(qiáng)類型的,但是null破壞了這個規(guī)則,它可以被賦值給任何對象
-
Java的設(shè)計是讓程序猿對指針無感知,但是null指針是個例外
- 它會是代碼變得很臃腫,到處都充斥著
if-else
的空檢查,甚至是多層嵌套,代碼可讀性下降 - null本身毫無意義,表示不了
無
前兩點(diǎn)不需要特別的說明,后兩點(diǎn)舉個例子來說明一下:假如一個人擁有一個手機(jī),每個手機(jī)都有生成廠商,每個廠商都會有個名字,用類表示的話:
- public class Person {
- private Phone phone;
- public Phone getPhone() {
- return phone;
- }
- }
- public class Phone {
- private Producer producer;
- public Producer getProducer() {
- return producer;
- }
- }
- public class Producer {
- private String name;
- public String getName() {
- return name;
- }
- }
在這個例子中,假如我們需要取到手機(jī)生成廠商的名字
- public String getPhoneProducerName(Person person) {
- return person.getPhone().getProducer().getName();
- }
由于不一定每個人都會有一個手機(jī),所有在調(diào)用 getProducer()
時可能會出現(xiàn) NullPointerException
。
一門設(shè)計語言本來就是來描述世界的,在這個事例中有的人有手機(jī),有的人也可能沒有手機(jī),所以在調(diào)用 person.getPhone()
返回的值就應(yīng)該包含有和無這兩種情況,現(xiàn)在通過返回 null
來表示無,但是在調(diào)用 getProducer()
卻又會拋出異常,這樣就不太符合現(xiàn)實(shí)邏輯;所以把 null
來用來表示 無
不合適
在遇到這種情況通常的做法是做null檢查,甚至是每個地方可能發(fā)生null指針的做檢查。
- public String getPhoneProducerName(Person person) {
- if (person.getPhone() == null) {
- return "無名字";
- }
- if (person.getPhone().getProducer() == null) {
- return "無名字";
- }
- return person.getPhone().getProducer().getName();
- }
這里我已經(jīng)試圖在減少代碼的層級,如果使用的是 if-else
,代碼的層級會更深,代碼可讀性下降。
Optional的簡單介紹
吐槽了那么多現(xiàn)狀的不好,現(xiàn)在可以祭出我們的解決方案了 Optional
;千呼萬喚始出來,猶抱琵琶半遮面;那 Optional
到底是個什么東西,我們一起來逐步解開它的面紗。
Optional
本身只是對對象的簡單包裝,如果對象為空,那么會構(gòu)建一個空的 Optional
;這樣一來 Optional
就包含了存在和不存在兩個情況, 接下來可以看下上面的例子改過之后
- public class Person {
- private Optional<Phone> phone;
- public Optional<Phone> getPhone() {
- return phone;
- }
- }
- public class Phone {
- private Producer producer;
- public Producer getProducer() {
- return producer;
- }
- }
- public class Producer {
- private String name;
- public String getName() {
- return name;
- }
- }
由于有的人可能沒有手機(jī),有的人有,所以 Phone
需要用 Optional
包裝起來;手機(jī)本身一定會有生產(chǎn)的廠商,廠商一定會有一個名字,所以這兩個不需要用 Optional
包裝起來。這里我們會發(fā)現(xiàn)使用了 Optional
會豐富代碼的語義,讓代碼更加符合現(xiàn)實(shí)。
而當(dāng)我們在調(diào)用 phone.getProducer().getName()
的時候不需要做null指針的檢查,如果說在這里發(fā)生了 NullPointerException
,說明這里數(shù)據(jù)本身是有問題的,不符合現(xiàn)實(shí),就應(yīng)該讓問題暴露出來,而不是像上面的代碼一樣把問題掩蓋。
Optional的常用方法使用
1. Optional的創(chuàng)建方法
- Optional<Person> empty = Optional.empty(); //申明一個空的Optional
- Optional<Person> person = Optional.of(new Person()); //包裝Person
- Optional<Person> person2 = Optional.of(null); //不允許的操作,傳入null 會拋出空指針異常
- Optional<Person> optionalPerson = Optional.ofNullable(null); //允許傳null, 返回一個空的Optional
2. Optional值的獲取方式
- map、flatMap 首先我們重新定義一下Phone類,除了有生產(chǎn)廠商之外,還有個型號;
- public class Phone {
- private String model;
- private Producer producer;
- public Producer getProducer() {
- return producer;
- }
- public String getModel() {
- return model;
- }
- }
當(dāng)我們需要獲取到手機(jī)的型號的時候可以這樣:
- Optional<Phone> optionalPhone = Optional.of(new Phone());
- Optional<String> model = optionalPhone.map(Phone::getModel);
當(dāng)我們需要通過Person對象獲取到Phone的型號,會想到這樣:
- Optional<Person> optionalPerson = Optional.of(new Person());
- optionalPerson.map(Person::getPhone).map(Phone::getModel);
當(dāng)我們寫出來的時候發(fā)現(xiàn)編譯器不能通過。是因?yàn)?nbsp;Person::getPhone
返回的是一個 Optional<Phone>
,調(diào)用 optionalPerson.map(Person::getPhone)
返回的就是 Optional<Optional<Phone>>
,所以再 .map
的就無法拿到手機(jī)型號,那如何能夠讓返回的結(jié)果不是 Optional<Optional<Phone>>
,而是 Optional<Phone>
呢?
這里需要用到另一個方法 flatMap
。 flatMap
和 map
的區(qū)別,我在剛開始學(xué)習(xí)的時候,看到了網(wǎng)上的各種解釋都很繞,看的很暈,最后直接打開源碼來看,發(fā)現(xiàn)實(shí)現(xiàn)很簡單,很容易理解,來看下源碼:
- public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
- Objects.requireNonNull(mapper);
- if (!isPresent())
- return empty();
- else {
- return Optional.ofNullable(mapper.apply(value));
- }
- }
- public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
- Objects.requireNonNull(mapper);
- if (!isPresent())
- return empty();
- else {
- return Objects.requireNonNull(mapper.apply(value));
- }
- }
map
方法在返回的時候會包裝一層 Optional
; flatMap
在返回的時候直接把函數(shù)的返回值返回了,函數(shù)的結(jié)果必須是 Optional
;那么在前面的例子中我們直接調(diào)用 flatMap
返回的結(jié)果就是 Optional<Phone>
- Optional<Person> optionalPerson = Optional.of(new Person());
- optionalPerson.flatMap(Person::getPhone).map(Phone::getModel);
- 取出
Optional
中的值對象:get、orElse、orElseGet、orElseThrow、ifPresent
- get() : 當(dāng)你明確知道Optional中有值的話可以直接調(diào)用該方法,當(dāng)Optional中沒有值是該方法會拋出異常
NoSuchElementException
;所以當(dāng)如果存在空值的話建議就不要調(diào)用該方法,因?yàn)檫@樣和做null檢查就沒有區(qū)別了 -
orElse(T other) : 提供一個默認(rèn)值,當(dāng)值不存在是返回這個默認(rèn)值
-
orElseGet(Supplier<? extends T> other) : 當(dāng)值不存在的時候會調(diào)用supper函數(shù),如果說返回這個默認(rèn)值的邏輯較多,那么調(diào)用這個方法比較合適;
-
orElseThrow(Supplier<? extends X> exceptionSupplier) : 當(dāng)值為空時會拋出一個自定義的異常
- ifPresent(Consumer<? super T> consumer) : 當(dāng)值不為空是會調(diào)用
consumer
函數(shù),如果值為空,那么這個方法什么都不做
- filter 過濾出滿足條件的對象 假如我們需要過濾出手機(jī)型號
IOS
的手機(jī),并打印出型號,代碼如下:
- Person person = new Person(Optional.of(new Phone("IOS")));
- Optional<Person> optionalPerson = Optional.of(person);
- optionalPerson.flatMap(Person::getPhone)
- .filter(phone -> "IOS".equals(phone.getModel()))
- .map(Phone::getModel)
- .ifPresent(System.out::println);
-
我們討論了null在Java程序的問題
- 介紹Java8中引入了
Optional
來表示有和無的情況以及初始化的方式 - 舉例說明了
Optional
中經(jīng)常使用到的方法