1. 程式人生 > >還在重複寫空指標檢查程式碼?考慮使用 Optional 吧!

還在重複寫空指標檢查程式碼?考慮使用 Optional 吧!

一、前言

如果要給 Java 所有異常弄個榜單,我會選擇將 NullPointerException 放在榜首。這個異常潛伏在程式碼中,就像個遙控炸彈,不知道什麼時候這個按鈕會被突然按下(傳入 null 物件)。

還記得剛入行程式設計師的時候,三天兩頭碰到空指標異常引發的 Bug,解決完一個,又在另一處碰到。那時候師兄就教我,不要相信任何『物件』,特別是別人給你的,這些地方都加上判斷。於是程式碼通常為會變成下面這樣:

if(obj!=null){
    // do something
}

有了這個防禦之後,雖然不用再擔心空指標異常,但是過多的判斷語句使得程式碼變得臃腫。

假設我們存在如下物件關係

原本為了獲取圖中的 name 屬性,原本一句程式碼就可以輕鬆完成。

Staff staff=..;
staff.getDepartment().getCompany().getName();

但是很不幸,為了程式碼的安全性,我們不得不加入空指標判斷程式碼。

Staff staff=..;
if (staff != null) {
    Department department = staff.getDepartment();
    if (department != null) {
        Company company = department.getCompany();
        if (company != null) {
            return company.getName();
        }
    }
}
return "Unknown";

當其中物件為 null 時,可以返回預設值,如上所示。也可以直接丟擲其他異常快速失敗。

雖然上面程式碼變得更加安全,但是過多巢狀 if 語句降低程式碼整體可讀性,提高複雜度。

所幸 Java 8 引入引入一個新類 Java.util.Optional<T>,依靠 Optional 類提供 API,我們可以寫出既安全又具有閱讀性的程式碼。

還在使用 JDK 6 ?那你也別急著關閉這篇文章。可以考慮使用 Guava Optional。不過需要注意的是,Guava Optional API 與 JDK 存在差異,以下以 JDK8 Optional 為例。

二、Optional API

2.1、Optional#of 與 Optional#ofNullable

Optional 本質是一個容器,需要我們將物件例項傳入該容器中。Optional 的構造方法為 private,無法直接使用 new 構建物件,只能使用 Optional 提供的靜態方法建立。

Optional 三個建立方法如下:

  • Optional.of(obj),如果物件為 null,將會丟擲 NPE。
  • Optional.ofNullable(obj),如果物件為 null,將會建立不包含值的 empty Optional 物件例項。
  • Optional.empty() 等同於 Optional.ofNullable(null)

只有在確定物件不會為 null 的情況使用 Optional#of,否則建議使用 Optional#ofNullable方法。

2.2、Optional#get 與 Optional#isPresent

物件例項存入 Optional 容器中之後,最後我們需要從中取出。Optional#get 方法用於取出內部物件例項,不過需要注意的是,如果是 empty Optional 例項,由於容器內沒有任何物件例項,使用 get 方法將會丟擲 NoSuchElementException 異常。

為了防止異常丟擲,可以使用 Optional#isPresent 。這個方法將會判斷內部是否存在物件例項,若存在則返回 true。

示例程式碼如下:

Optional<Company> optCompany = Optional.ofNullable(company);
// 與直接使用空指標判斷沒有任何區別
if (optCompany.isPresent()) {
    System.out.println(optCompany.get().getName());
}

仔細對比,可以發現上面用法與空指標檢查並無差別。剛接觸到 Optional ,看到很多文章介紹這個用法,那時候一直很疑惑,這個解決方案不是更加麻煩?

後來接觸到 Optional 其他 API,我才發現這個類真正有意義是下面這些 API。如果使用過 Java8 Stream 的 API,下面 Optional API 你將會很熟悉。

2.3、Optional#ifPresent

通常情況下,空指標檢查之後,如果物件不為空,將會進行下一步處理,比如列印該物件。

Company company = ...;
if(company!=null){
    System.out.println(company);
}

上面程式碼我們可以使用 Optional#ifPresent 代替,如下所示:

Optional<Company> optCompany = ...;
optCompany.ifPresent(System.out::println);

使用 ifPresent 方法,我們不用再顯示的進行檢查,如果 Optional 為空,上面例子將不再輸出。

2.4、Optional#filter

有時候我們需要某些屬性滿足一定條件,才進行下一步動作。這裡假設我們當 Company name 屬性為 Apple,列印輸出 ok。

if (company != null && "Apple".equals(company.getName())) {
    System.out.println("ok");
}

下面使用 Optional#filter 結合 Optional#ifPresent 重寫上面的程式碼,如下所示:

Optional<Company> companyOpt=...;
companyOpt
        .filter(company -> "Apple".equals(company.getName()))
        .ifPresent(company -> System.out.println("ok"));

filter 方法將會判斷物件是否符合條件。如果不符合條件,將會返回一個空的 Optional

2.5、Optional#orElse 與 Optional#orElseThrow

當一個物件為 null 時,業務上通常可以設定一個預設值,從而使流程繼續下去。

String name = company != null ? company.getName() : "Unknown";

或者丟擲一個內部異常,記錄失敗原因,快速失敗。

if (company.getName() == null) {
    throw new RuntimeException();
}

Optional 類提供兩個方法 orElseorElseThrow ,可以方便完成上面轉化。

// 設定預設值
String name=companyOpt.orElse(new Company("Unknown")).getName();

// 丟擲異常
String name=companyOpt.orElseThrow(RuntimeException::new).getName();

如果 Optional 為空,提供預設值或丟擲異常。

2.6、Optional#map 與 Optional#flatMap

熟悉 Java8 Stream 同學的應該瞭解,Stream#map 方法可以將當前物件轉化為另外一個物件, Optional#map 方法也與之類似。

Optional<Company> optCompany = ...;
Optional<String> nameopt = optCompany.map(Company::getName);

map 方法可以將原先 Optional<Company> 轉變成 Optional<String> ,此時 Optional 內部物件變成 String 型別。如果轉化之前 Optional 物件為空,則什麼也不會發生。

另外 Optional 還有一個 flatMap 方法,兩者區別見下圖。

Department#getCompany返回物件為 Optional<Company>

三、程式碼重構

上面我們學習了 Optional 類主要 API ,下面我們使用 Optional 重構文章剛開頭的程式碼。為了方便讀者對比,將上面的程式碼複製了下來。

程式碼重構前:

if (staff != null) {
    Department department = staff.getDepartment();
    if (department != null) {
        Company company = department.getCompany();
        if (company != null) {
            return company.getName();
        }
    }
}
return "Unknown";

首先我們需要將 StaffDepartment 修改 getter 方法返回結果型別改成 Optional

public class Staff {
    private Department department;
    public Optional<Department> getDepartment() {
        return Optional.ofNullable(department);
    }
    ...
}
public class Department {

    private Company company;
    public Optional<Company> getCompany() {
        return Optional.ofNullable(company);
    }
    ...
}

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

然後綜合使用 Optional API 重構程式碼如下:

Optional<Staff> staffOpt =...;
staffOpt
        .flatMap(Staff::getDepartment)
        .flatMap(Department::getCompany)
        .map(Company::getName)
        .orElse("Unknown");

可以看到重構之後程式碼利用 Optional 的 Fluent Interface,以及 lambda 表示式,使程式碼更加流暢連貫,並且提高程式碼整體可讀性。

四、幫助文章

1、Tired of Null Pointer Exceptions? Consider Using Java SE 8's Optional!
3、Optionals: Patterns and Good Practices
3、Java8 in Action

歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:studyidea.cn

相關推薦

複寫指標檢查程式碼考慮使用 Optional

一、前言 如果要給 Java 所有異常弄個榜單,我會選擇將 NullPointerException 放在榜首。這個異常潛伏在程式碼中,就像個遙控炸彈,不知道什麼時候這個按鈕會被突然按下(傳入 null 物件)。 還記得剛入行程式設計師的時候,三天兩頭碰到空指標異常引發的 Bug,解決完一個,又在另一處

Java程式設計師3年月薪沒到2萬,乾脆考慮轉行

最近特別想說幾句話,但是思來想去又覺得這些話不該說,因為肯定會招來很多很多程式設計師的攻擊甚至是謾罵。 我就想了,何必呢,像我這樣溫順又沒有攻擊力的耿直boy,專心寫點我愛寫的電影內容多好,幹嘛說那樣吃力不討好的話呢?所以,我決定——說。 但我還是要先預個警,本文內容一定

Java8新特性之指標異常的剋星Optional

Java8新特性系列我們已經介紹了Stream、Lambda表示式、DateTime日期時間處理,最後以“NullPointerException” 的剋星Optional類的講解來收尾。 背景 作為開發人員每天與NullPointerException鬥智鬥勇。每接收到引數或呼叫方法獲得值得判斷一下是否為n

java8使用Optional來避免指標異常(簡化程式碼

在最近的開發中遇到不少java.lang.NullPointerException異常 ,而為了避免這個異常,不免要利用if-else來進行判斷。比如如下的程式碼: public static void main(String[] args) { Lis

資料庫有資料,但查詢不到資料,沒查到指標

              今天在做springMVC專案的時候,因為粗心,忘了在控制層注入service的類上面加上@Autowired,執行程式碼就一直報空指標,把hql翻譯成sql放入資料庫查詢

Android 使用自定義註解代替複寫findViewById程式碼

效果 每次新建頁面控制元件的findViewById是每個android開發者的痛苦。在這方面已經有很多第三方框架幫我們解放了雙手,這次就是利用註解來解決findViewById。 public class ObserverActivity extend

利用 LeakCanary 來檢查 Android 記憶體洩漏 6.0以上版本指標解決

1.3. 在6.0預覽版報錯 * FAILURE: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a nul

Jdk14都要出了,不能使用 Optional優雅的處理指標

# 1. 前言 > 如果你沒有處理過空指標,那麼你不是一位真正的 Java 程式設計師。 ![](https://img2018.cnblogs.com/blog/1202902/201911/1202902-20191105085108008-1443003546.png) 空指標確實會產

打破你的認知Java指標居然能這樣玩,90%人不知道…

相信在座的各位都遇到過空指標異常,不甚其煩,本文不是教你避免空指標,而是一些對空指標其他方面的理解。 本文可能有點另類,也可能會打破你對空指標的認知。 ## 1、null.method() 空指標? 我們知道呼叫一個物件的方法,如果物件為 `null` 肯定會報空指標錯誤的,但你確定一定會嗎? 不一定

iOS數組的去,判,刪除元素,刪除復元素 等

ack 如果 tle sar abc 朋友 計數 led trac 一: 去重 有時需要將NSArray中去除重復的元素,而存在NSArray中的元素不一定都是NSString類型。今天想了想,加上朋友的幫助,想到兩種解決辦法,先分述如下。 1.利用NSDictionar

PHP 類型判斷和NULL,檢查

ron cal 簡單 www its 類型 cti 哪些 是否 PHP是一種寬松類型的編程語言,在函數中對傳入的參數值的“類型”以及”值是否為空或者NULL“進行檢查是不可缺少的步驟。 類型檢查 從PHP

指標+指標+萬能指標

2.3 指標大小 l  使用sizeof()測量指標的大小,得到的總是:4或8 l  sizeof()測的是指標變數指向儲存地址的大小 l  在32位平臺,所有的指標(地址)都是32位(4位元組) l  在64位平臺,所有的指標(地址)都是64位(8位元組)

#程式設計師節公司請國外妹子表演,程式設計師吐槽:怎麼讓我安心寫程式碼

網際網路行業就目前來說是個火熱行業,高薪就讓很多人選擇進入了這個行業,現在網際網路行業幾乎就是搶時間,加班幾乎是每天必備的專案,前幾天程式設計師晒出自己的加班時間,一個月達到了120小時,但是薪資待遇也是非常好的,最近程式設計師節就有程式設計師晒出了自己公司的節日福利。 在這裡我推薦下自己整

xxx.class.getClassLoader().getResource("xxx").getPath()這句話報錯指標

今天跟著網上的教程自己寫了一個簡易的spring的IOC仿寫程式,然後解析xml檔案都是自己寫的,在執行的時候去獲取.xml的路徑的時候呼叫了xxx.class.getClassLoader().getResource("xxx").getPath()這個方法去獲取,然後報錯空指標,debug以後發

android studio 使用butterknife 報指標 異常

  使用butterKnife 時,報錯。 在使用butterknife 8.0以上的版本是,出現空指標錯誤,記錄如下: 錯誤使用: 在寫一個Demo 的時候使用了ButterKnife ,是直接從android  studio 上面下載匯入的 jar 包

SqlSession指標異常

在學習使用mybatis時,我寫了一個簡單的測試程式碼。建立SqlSession,然後用SqlSession插入一條資料到資料庫中,無奈一直報空指標異常。 private SqlSession session; @Before public void init(){

淺談指標和棧,堆記憶體

/**    * 堆記憶體(heap):儲存每一個物件的屬性,使用一個物件時,一定需要一個對應堆記憶體的指向,而堆記憶體空間的開闢需要用關鍵字     *new,每一個物件在剛剛例項化後,裡面的屬性都是其對應資料型別的預設值,一塊堆記憶體可以被多個棧

合併兩個有序連結串列(注意指標異常)

將兩個有序連結串列合併為一個新的有序連結串列並返回。新連結串列是通過拼接給定的兩個連結串列的所有節點組成的。  要注意判斷兩個結點是否為空結點,不然會出現空指標異常 /** * Definition for singly-linked list. * public class

ListView優化時,控制元件行,報指標

 FATAL EXCEPTION: main                                   &n