實戰系列:使用Java8 Optional類優雅解決空指針問題

2021-02-20 腳本之家

Java8 由Oracle在2014年發布,是繼Java5之後最具革命性的版本。Java8吸收其他語言的精髓帶來了函數式編程,lambda表達式,Stream流等一系列新特性,學會了這些新特性,可以讓你實現高效編碼優雅編碼。

1. 不受待見的空指針異常

有個小故事:null引用最早是由英國科學家Tony Hoare提出的,多年後Hoare為自己的這個想法感到後悔莫及,並認為這是"價值百萬的重大失誤"。可見空指針是多麼不受待見。

NullPointerException是Java開發中最常遇見的異常,遇到這種異常我們通常的解決方法是在調用的地方加一個if判空。

if判空越多會造成過多的代碼分支,後續代碼維護也就越來越複雜。

2. 糟糕的代碼

比如看下面這個例子,使用過多的if判空。

Person對象裡定義了House對象,House對象裡定義了Address對象:

public class Person {
    private String name;
    private int age;
    private House house;
    public House getHouse() {
        return house;
    }
}
class House {
    private long price;
    private Address address;
    public Address getAddress() {
        return address;
    }
}
class Address {
    private String country;
    private String city;
    public String getCity() {
        return city;
    }
}

現在獲取這個人買房的城市,那麼通常會這樣寫:

public String getCity() {
    String city = new Person().getHouse().getAddress().getCity();
    return city;
}

但是這樣寫容易出現空指針的問題,比如這個人沒有房,House對象為null。接著你會改造這段代碼,加上很多判斷條件:

public String getCity2(Person person) {
    if (person != null) {
        House house = person.getHouse();
        if (house != null) {
            Address address = house.getAddress();
            if (address != null) {
                String city = address.getCity();
                return city;
            }
        }
    }
    return "unknown";
}

為了避免空指針異常,每一層都加上判斷,但是這樣會造成代碼嵌套太深,不易維護。你可能想到如何改造上面的代碼,比如加上提前判空退出:

public String getCity3(Person person) {
    String city = "unknown";
    if (person == null) {
      return city; 
    }
    House house = person.getHouse();
    if (house == null) {
        return city;
    }
    Address address = house.getAddress();
    if (address == null) {
        return city;
    }
    return address.getCity();
}

但是這樣簡單的代碼已經加入了三個退出條件,非常不利於後面代碼維護。那怎樣才能將代碼寫的優雅一點呢,下面引入今天的主角"Optional"。

3. 解決空指針的"銀彈"

從Java8開始引入了一個新類 java.util.Optional,這是一個對象的容器,意味著可能包含或者沒有包含一個非空的值。下面重點看一下Optional的常用方法:

public final class Optional<T> {
    // 通過指定非空值創建Optional對象
    // 如果指定的值為null,會拋空指針異常
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }
    // 通過指定可能為空的值創建Optional對象
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
    // 返回值,不存在拋異常
    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
    // 如果值存在,根據consumer實現類消費該值
    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }
    // 如果值存在則返回,如果值為空則返回指定的默認值
    public T orElse(T other) {
        return value != null ? value : other;
    }
    // map flatmap等方法與Stream使用方法類似,這裡不再贅述,讀者可以參考之前的Stream系列。
}

以上就是Optional類常用的方法,使用起來非常簡單。

4. Optional使用入門

(1)創建Optional實例

創建空的Optional對象。可以通過靜態工廠方法Optional.Empty() 創建一個空的對象,例如:
Optional<Person> optionalPerson = Optional.Empty();

Person person = new Person();
Optional<Person> optionalPerson = Optional.of(person);

Person person = null; // 可能為空
Optional<Person> optionalPerson = Optional.of(person);

(2)常用方法ifPresent

如果值存在,則調用consumer實例消費該值,否則什麼都不執行。舉個慄子:

String str = "hello java8";
// output: hello java8
Optional.ofNullable(str).ifPresent(System.out::println);
String str2 = null;
// output: nothing
Optional.ofNullable(str2).ifPresent(System.out::println);

filter, map, flatMap在三個方法在前面講Stream的時候已經詳細講解過,讀者可以翻看之前寫的文章,這裡不再贅述。

orElse

如果value為空,則返回默認值,舉個慄子:

public void test(String city) {
    String defaultCity = Optional.ofNullable(city).orElse("unknown");
}

orElseGet如果value為空,則調用Supplier實例返回一個默認值。舉個例子:

public void test2(String city) {
    // 如果city為空,則調用generateDefaultCity方法
    String defaultCity = Optional.of(city).orElseGet(this::generateDefaultCity);
}
private String generateDefaultCity() {
    return "beijing";
}

orElseThrow如果value為空,則拋出自定義異常。舉個慄子:

public void test3(String city) {
    // 如果city為空,則拋出空指針異常。
    String defaultCity = Optional.of(city).orElseThrow(NullPointerException::new);
}

5. 使用Optional重構代碼

再看一遍重構之前的代碼,使用了三個if使代碼嵌套層次變得很深。

// before refactor
public String getCity2(Person person) {
    if (person != null) {
        House house = person.getHouse();
        if (house != null) {
            Address address = house.getAddress();
            if (address != null) {
                String city = address.getCity();
                return city;
            }
        }
    }
    return "unknown";
}

使用Optional重構

public String getCityUsingOptional(Person person) {
    String city = Optional.ofNullable(person)
            .map(Person::getHouse)
            .map(House::getAddress)
            .map(Address::getCity).orElse("Unknown city");
    return city;
}

只使用了一行代碼就獲取到city值,不用再去不斷的判斷是否為空,這樣寫代碼是不是很優雅呀。

總結:使用optional類可以很優雅的解決項目中空指針的問題,但是optional也不是萬能的哦,小夥伴們要適度使用。趕緊用Optional重構之前寫的項目吧~

推薦閱讀:

巧用Java8中的Stream,讓集合操作6到飛起!!!

9月份Github上最熱門的Java開源項目

Java新特性:數據類型可以扔掉了?

相關焦點

  • 新技術:使用視圖綁定替代 findViewById
    解決方案: 在 Activity 中使用視圖綁定時,一定要將綁定對象的 root 屬性傳入 setContentView() 方法中。findViewById 是許多用戶可見 bug 的來源: 我們很容易傳入一個布局中根本不存在的 id,從而導致空指針異常而崩潰;由於此方法類型不安全,也很容易使人寫出像 findViewById<TextView>(R.id.image) 這樣的,導致類型轉換錯誤的代碼。為了解決這些問題,視圖綁定把 findViewById 替換成了更加簡潔和安全的實現。
  • iOS 12.0.1更新:iPhone XS系列充電問題已解決
    蘋果公司今天凌晨發布了適用於 iPhone 和 iPad 的 iOS 12.0.1 更新,這是蘋果自 9 月發布 iOS 12 和 iPhone XS 系列手機以來第一次對發現的
  • 指針幣1鑽秒殺專場明日來襲,幸運指針節後特別回饋
    (每個售價1鑽) 指針幣僅在上述指定時間段內刷新,敬請留意正確時間。2017年2月3日0:00 -2017年2月9日23:591.完成任務後,請及時在「活動」內領取指定檔位獎勵3.8級穿透晶石包:打開隨機獲得:8級物穿晶石*1、8級法穿晶石*1中的任意一種2017年2月3日 0:00-2017年2月9日23:59幸運指針感恩回饋!獲獎機率大幅提升!豪禮拿到手軟!
  • 關於C++ std::string類內存布局的探究
    對於一個自定義的class,一個指向它的指針和一個指向int型整數的指針有何不同呢?從內存的角度來看,沒有什麼不同!為什麼?為什麼同一平臺上不同類型的指針大小都是8bytes?這是因為不同機器上的指針的尋址範圍,必須涵蓋當前運行環境的最大尋址範圍。換句話說,一個指針的大小,取決於當前運行環境的字長(word)。
  • 時間管理:如何優雅地使用「日曆」
    倘若你不習慣用手機記事,你完全可以使用帶「日曆」的筆記本(工作手冊)。但是,無論使用手機還是筆記本,不管三七二十一地把大量的事情堆砌在「日曆」裡,這顯然不是一個優雅的習慣。  「日曆」應該記錄的是:「今天」一定要做,一定要知道的事。
  • 活動 | 液空工會YouthAL系列--液空工會杯「液空之音」歌唱比賽官宣
    9月26日,液空工會「液空之音」悄然啟動。 說是悄然,是因為竟然還有好多人不知道這麼重要的活動!!!好吧,我們錯了.現在,官宣來了:「液空之音」是液空工會首屆歌唱比賽,歌唱比賽,歌唱比賽,這裡將誕生液空工會第一位歌王or歌后。所有愛好唱歌的液空人都快來參加!
  • 壓力表指針指向紅色 這種滅火器別買
    原標題:壓力表指針指向紅色 這種滅火器別買   近日,成都市公安消防局對成都各大商鋪的滅火器展開檢查。昨日,大邑縣消防大隊在檢查中發現,不少店鋪內的新滅火器,有的因為壓力不足,已經無法使用。  在大邑縣一家消防器材店,記者見到貨架上擺放了幾十個滅火器。
  • 這個老大難問題終於解決了
    《一下雨就積水成「河」 秦皇島這個小區居民出行太難了》現在,這個問題終於要解決了。連日來,在翠堤灣小區東南側,拌和機、挖掘機、吊車等工程車輛引擎轟隆作響。海港區市政工程管理處施工人員身影忙碌,破槽、布管、澆築、焊接、回填、霧炮降塵有序逐次進行,一根根深黑色的排水管被妥善連接……汙水管網入網改造工程打破了冬日的冷清。
  • 小問題也是問題:4種停車架解決停車難
    當你開始熱衷於騎行後,一定有被單車存放這個問題困擾過。
  • 反空汙大遊行 環團提5訴求
    南部反空汙聯盟召開「1111高雄反空汙遊行」行前記者會,呼籲高雄市長參選人針對高雄空汙問題,提出改善對策和具體時程。記者蔡容喬/攝影     南部秋冬進入空汙季節,南部反空汙大聯盟29日舉行「1111高雄反空汙遊行」行前記者會,呼籲高雄市長參選人針對高雄空汙問題,提出改善對策和具體時程,但月前向4位市長候選人發出空汙改善訴求承諾書,陳其邁、韓國瑜都籤署承諾書,蘇盈貴和璩美鳳兩位候選人則沒有回應。
  • Jetpack 實戰:神奇寶貝
    PokemonGo先來介紹一下 Jetpack 實戰項目 PokemonGo, 包含了以下功能:使用 Data Mapper 分離數據源 和 UIKotlin Flow 結合  Retrofit2 + RoomActivity 、Fragment、ViewModel 結合 Flow 三種使用方式使用 ConflatedBroadcastChannel
  • 利用空拍機創作的巨幅光繪攝影
    聖誕節將至,街頭巷尾又被各式各樣的聖誕彩燈裝飾了一番,只是這些燈飾大多數都是千篇一律,似乎欠缺些新意,但以下這個由空拍機加上光繪攝影製作的飛天燈飾
  • 吃西餐,如何優雅使用餐巾布?
    【新朋友】請點擊標題下方藍字關注
  • iPhone 6好消息:供貨問題解決了!
    點擊上面的藍字關注iPhone頻道☀ iPhone頻道是微信裡最熱的、粉絲最多的蘋果類公眾號!這裡蘋果迷的聚集地!
  • 每日郵報 | 英國中學生居然看不懂指針鐘錶
    校長們稱,因為青少年看不懂指針時間,指針時鐘將從考場裡消失,取而代之的是數字鐘。Digital devices will be installed into exam halls because GCSE and A-level students complained that they were unable to read the correct time.
  • 【瑪格麗】溫暖羽絨,優雅出眾
    【瑪格麗】女裝品牌升級12月25日重裝啟幕全場貨品200升280活動時間:12月25日-27日電話:0951-6099453地址:國芳百貨三樓瑪格麗專櫃2015秋冬羽絨服系類採用經典藍白撞色設計,配有細節口袋和白色裝飾邊,輔以現代建築裝飾線條,為您冬天展現卓爾不群的優雅氣質。
  • 解決不了問題,就解決提出問題的人
    02、解決不了問題,就解決提出問題的人小時候,我媽告訴我隔夜的水不能喝,我問她:「早上6點燒的水,下午3點能喝嗎?」我媽說能喝。我又問:「晚上9點燒的水,早上6點能喝嗎?」我媽說不能喝。我說:「同樣幾個小時的水,同樣的儲存環境,晚上溫度低更利於保存,為啥後者不能喝?」
  • 解鎖新姿勢:探討複雜的 if-else 語句「優雅處理」的思路
    作者:hyzhan43juejin.im/post/5def654f51882512302daeef前言在之前文章說到,簡單 if-else,可以使用 衛語句 進行優化。策略模式是定義一系列的算法,把它們一個個封裝起來, 並且使它們可相互替換。比如上述需求,有返利、有打折、有折上折等等。這些算法本身就是一種策略。並且這些算法可以相互替換的,比如今天我想讓 白銀會員優惠50,明天可以替換為 白銀會員打9折。說了那麼多,不如編碼來得實在。