萬惡的NPE差點讓我半個月工資沒了

引言

最近看到《阿里巴巴Java開發手冊》第11條規範寫到:

防止 NPE ,是程式設計師的基本修養

NPE(Null Pointer Exception)一直是開發中最頭疼的問題,也是最容易忽視的地方。記得剛開始工作的時候所在的項目組線上出現最多的bug不是邏輯業務bug而是NPE,所以後面項目組出了一個奇葩的規矩,線上如果誰出現一個NPE的問題就罰款100元,用作團建費用。如果項目組每個人一個月都出現個兩三個NPE的話。那麼項目組是不是每個月都可以去團建下(自己掏錢海吃海喝,心不心疼)。不過自從這個規矩實施以來,線上的NPE就漸漸的少了,從最初的一個月團建一次到最後的半年團建一次。大家寫程式碼都比較謹慎了,只要用到對象或者集合的時候二話不說上來先判空,所以產生的NPE就少了。

業務中返回結果的空值

在我們常見的業務開發中是不是經常會有這樣的介面:

package com.workit.demo.nullexcption;

import com.workit.demo.proxy.User;

import java.util.List;

public interface IUserSearchService {
  /**
   * 查詢用戶列表
   * @return
   */
  List<User> listUser();

}

這個介面是不是存在兩個潛在的問題?

  • listUser這個方法 如果沒有數據,那它是返回空集合還是null呢?
  • getUserById 如果根據ID沒有找到用戶,是拋異常還是返回null呢?
    首先我們先看下listUser這個方法的實現:
 public List<User> listUser() {
        List<User> userList = userRepository.listUser();
        if (null == userList || userList.size() == 0) {
            return null;
        }
        return userList;
    }

這種實現如果調用者是一個嚴謹的人或者像我這樣被NPE罰款買過單的人,是會對返回結果進行null的判斷。如果調用者並非謹慎的人或者剛剛入門的人,他就會按照自己的理解去調用介面,拿到結果就不管三七二十一上來對結果就是一頓循環操作,而不進行是否為null的條件判斷,如果這樣的話,是非常危險的,它很有可能出現空指針異常!這就是在程式碼中埋了一個定時炸彈,不知道什麼時候就會爆炸。
在這裡插入圖片描述
由於存在這種不安全的隱患我們可以看下第二種實現:

  public List<User> listUser() {
        List<User> userList = userRepository.listUser();
        if (null == userList || userList.size() == 0) {
            return new ArrayList<>();
        }
        return userList;
    }

對於這種實現它一定會返回List,即使沒有數據,也會返回一個空集合。通過以上的修改,我們成功的避免了有可能發生的空指針異常,這樣的寫法更安全!
那針對於上面的兩種實現,一個是需要調用者進行判空,一個是提供介面的人返回默認值。那我們到底應該用哪種方式呢?這種情況《阿里巴巴開發手冊》也有明確規定:
在這裡插入圖片描述所以還是那句話使用任何對象或者集合之前記得先判空

業務中請求參數空值

 /**
   * 根據用戶ID查詢當前用戶
   * @param id
   * @return
   */
  User getUserById(Integer id);

這個介面的描述,你能確定入參id一定是必傳的嗎? 我覺得答案應該是:不能確定。除非介面的文檔注釋上加以說明。那麼我們應該怎樣來約束入參呢?

  • 強制約束
  @Override
    public User getUserById(Integer id) {
        if (Objects.isNull(id)){
            throw new IllegalArgumentException("id不能為空");
        }
        return null;
    }

通過jsr 303進行嚴格的約束聲明配合AOP的操作進行驗證。

User getUserById(@NotNull  Integer id);

其他需要注意的NPE

switch中的空指針異常

看下面的列子妥妥的NPE

 public static void main(String[] args) {
        eat(null);
    }
     enum EatType{
         Breakfast,Lunch,Dinner;
    }
    public static void eat(EatType eatType){
        switch(eatType){
            case Breakfast:
                System.out.println("吃早飯");
                break;
            case Lunch:
                System.out.println("吃中飯");
                break;
            case Dinner:
                System.out.println("吃晚飯");
                break;
            default:
                System.out.println("輸入錯誤");
                break;
        }
    }
資料庫的sum函數

在這裡插入圖片描述
如果price對應的所有的值為null,那麼算出來的和為null
在這裡插入圖片描述
如果採用ifnull函數就可以求和就是0這樣就可以避免空指針。
在這裡插入圖片描述

使用Map類集合時需要注意存儲值為null的時候

筆者就是由於存儲了null值造成生產事故,差點被開除了!詳細介紹可以閱讀以前文章《Java采坑記》
在這裡插入圖片描述

使用 java.util.stream.Collectors 類的 toMap()方法注意value為空時

在這裡插入圖片描述
如果項目裡面就是有null值怎麼辦呢?可以用下面幾種方法來解決:

  • 過濾值為null
  • 換一種寫法
  • 據說這個問題java9就修復了,所以也可以嘗試升級jdk
  List<Pair<String, Double>> pairArrayList = new ArrayList<>(2);
        pairArrayList.add(new Pair<>("version1", 4.22));
        pairArrayList.add(new Pair<>("version2", null));
        // 第一種過濾值為null的
        Map<String, Double> map = pairArrayList.stream().filter(p-> Objects.nonNull(p.getValue())).collect(
                Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
        System.out.println(map.toString());
        // 換一種實現方式
        LinkedHashMap<Object, Object> collect = pairArrayList.stream().collect(LinkedHashMap::new, (m, v) -> m.put(v.getKey(), v.getValue()), LinkedHashMap::putAll);
        System.out.println(collect.toString());

輸出結果

{version1=4.22}
{version1=4.22, version2=null}

這個方法還有一個坑如果key相同也會拋異常,感興趣的同學可以動手試試。

使用 Collection 介面任何實現類的 addAll()方法時,都要對輸入的集合參數進行NPE 判斷。

在這裡插入圖片描述

三目運算符可能產生NPE

在這裡插入圖片描述

那麼如何有效的避免NPE呢

  • 使用對象或者集合之前記得先判空。
  • 使用JDK一些API的方法記得要點進源碼去大概看看,不要隨便拿來就用。
  • 單元測試要對空值進行測試,保證程式的健壯性。
  • 合理的使用JDK1.8提供的Optional來避免NPE
  • 提供介面時候需要對非空參數進行說明,並且對非空參數進行校驗,不要太相信調用者。
  • 調用介面的時候一定要對介面返回值進行判空,不要太相信介面提供者。(這個肯定會有值的)。
  • 小心使得萬年船

結束

  • 由於自己才疏學淺,難免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
  • 如果你覺得文章還不錯,你的轉發、分享、讚賞、點贊、留言就是對我最大的鼓勵。
  • 感謝您的閱讀,十分歡迎並感謝您的關注。

參考
《阿里巴巴泰山版Java開發手冊》