那些jdk中坑你沒商量的方法

Yrion發表於2020-08-31

前言:jdk作為我們每天必備的呼叫類庫,裡面大量提供了基礎類供我們使用.可以說離開jdk,我們的java程式碼寸步難行,jdk帶給我們的便利可謂是不勝列舉,但同時這些方法在使用起來也存在一些坑,如果不注意就很容易掉入到陷阱裡面,導致程式丟擲錯誤。jdk中的很多方法都不會做非null判斷,可能設計jdk的作者預設開發者已經處理好null值了.不過這個設計可能會造成很嚴重的後果,實在是暗藏殺機。比如今天早上我們查了一筆訂單沒有退款,查了一早上最終才發現是同事寫的程式碼的BigDecimal的subtract方法的值沒有做非null判斷處理導致程式丟擲了空指標異常,看似簡單的異常卻直接無法讓很多訂單退款,是在是小問題造成大事故。而要修補退款這個問題,要耗費很多時間去修補,實在是讓人覺得麻煩。出錯的成本太高,本期我們就來看看jdk中那些坑你沒商量的方法,這些方法很常見,相信你一定遇到過。

一:String.valueOf()方法的陷阱

String.valueOf()是String提供的一個型別轉換的方法,我們來看一下案發現場(程式碼簡化過後的):

        Map<String, Object> userInfo = userService.getUserInfoById(userId);

        Object userNameObject = userInfo.get("name");

        String userName = String.valueOf(userNameObject);
        // 判空
        if(userName!=null&&userName.length()>0) {

            String message = getMessage(userName);

            smsService.send(message);
        }

 

這段程式碼是簡化過的,主要作用就是通過使用者服務根據id獲取使用者資訊傳送簡訊,不過後來有客訴反應,最後的簡訊成了尊敬的"null"你好,xx等。開發第一時間看了程式碼,覺的沒有問題啊,為什麼簡訊內容會出現使用者名稱為null呢,不是經過了非空判斷的嗎?後來經過定位發現了問題所在:首先使用者的名字裡有特殊的emoji符號,從資料庫獲取的時候因為符號轉義的時候為null了,接下來才是重點: 

 這裡是重點,也是最大的坑人之處,注意這裡返回了一個"null"的字串,而不是null。這兩個是有很大區別的,當進行非空判斷的時候,返回的是ture。

正確的處理方法:

 

 二:Integer.parseInt()方法很矯情

事故現場:一次業務場景為拉取訂單,打出訂單列表記錄,財務人員需要拉出對賬,結果總是發現少很多資料,很奇怪的一個現象。還好財務發現了,要不然和第三方財務對賬就會虧很多錢...最終發現是訂單的一個欄位值轉Integer出錯了,那個訂單下的欄位值是120.0通過

Integer.parseInt直接報錯了,恰好開發人員認為這段開發肯定沒問題,因此就沒有catch異常,最後找了很久才發現(因為涉及到第三方,還讓別人查了半天...). 知道真相的我們都有點汗顏,這麼丁點的錯誤排查了很久,實在是不應該啊。

Integer.parseInt()方法用於將字串轉化為Integer型別的方法,此方法的適用方向就顯得比較窄,因為是String型別的引數,沒有任何限定,當在傳入一些比如50.0、20L、30d、40f這類資料的情況下,

我們來看一個栗子::

 會丟擲異常NumberFormatException:

  事實上對於這樣的資料,比如小數、浮點資料、long型資料它都可以自動轉換,而不是給我們丟擲煩人的報錯資訊,如果預先知道是整數或者小數,可以用Bigdecimal轉換(注意此方法不適用於double和float、Long型別的資料,比如10d,20L)

 對於浮點型別、long型別的資料可以用以下方法來處理:

推薦使用hutool的NumberUtil.parseInt()方法,充分考慮到了浮點、long、小數等型別資料可能帶來的解析異常的問題,hutool是一個國人開源的工具類庫,這裡實名推薦,容錯性和處理異常能力很強

三:Bigdecimal的除法坑你沒商量

   眾所周知,BigDecimal是處理金額最有效的資料型別,一般進行財務報表計算的時候為了防止金額出現錯誤,一般情況下都會採用Bigdecimal,而double、float都會存在些許的誤差。你開開心心的用Bigdecimal進行了計算,而最終的結果返回卻有問題,我們來看一個例子:

 

 常見的除法用起來沒有任何絲毫的問題,妥妥的.但是一旦程式中的資料出現以下情況,如果用Bigdecimal來接受前端的引數,而前端的引數是使用者輸入不確定的,一旦出現如下的資料,我們來看看結果:

執行結果一看,居然報錯了哎:

 

 這就是BidDecimal的坑,一旦返回的結果是無限迴圈小數,就會丟擲ArithmeticException。因此在進行Bigdecimal除法的時候,需要進行保留小數的處理,正確的處理姿勢:

 

 四:Collections.emptyList()此list非彼list

我們先來看一個sample:

  public List<String> getUserNameList(String userId) {
        
        List<String> resultList = Collections.emptyList();
        try {
            resultList = userDao.getUserName(userId);
        } catch (Exception ex) {
            logger.info(ex);
        }
        return resultList;
    }

 這樣會丟擲錯誤,主要問題在於Collections.emptyList()並非我們平時看到的List,此list不支援add、remove方法,否則會丟擲operationNotSupportException:

 結果丟擲異常:

 

 原因是:Collections.emptyList返回的並不是我們平時認識的那個list,它是一個內部常量類:

public static final List EMPTY_LIST = new EmptyList<>();

這個list並不具有add、remove元素的能力,我猜想是因為jdk設計之初的想法是將這個list作為一種只讀的list,並不提供資料的寫入能力,因此它僅可作為一種 空值返回,無法進行刪除、新增操作。

 五:list可以一邊刪除一邊遍歷嗎?

我們再來看一個簡單的例子:

 很不幸,又雙叒叕報錯了:

  

仔細翻閱原始碼會發現,每次remove之前會檢查元素的條數,如果發現預期的modCount和當前的modCount不一致就會丟擲這個異常.modCount是list中用來記錄修改次數的一個屬性,當對元素進行統計的時候就會對該元素加1,而當對list邊遍歷邊刪除的話,就會造成

excepted與modCount不一致,從而丟擲異常。

 

正確的刪除姿勢就是使用Iterator.remove進行遍歷刪除,可以規避這個問題。

 六:總結

    jdk的設計者有兩個很大的特點:①大多不會做非null判斷出現錯誤直接throw new Exception,容錯性很差,在實際開發中,面對jdk一定要謹慎使用,jdk提供了便利的同時,也有一些我們使用上的盲區,應該養成多看原始碼,多注意錯誤性處理,防止在小問題上栽大跟頭。回到最開始說的那個subtract方法的問題,因為這個問題等需要我處理完之後使用者才能收到退款,這直接造成了使用者體驗直線下降,而部分使用者還直接打電話投訴。同事一個小小的不謹慎和馬虎就給公司造成了很多負面影響,技術問題雖然不大但是帶來的業務影響範圍很嚴重。所以我們必須防微杜漸,小小的問題都得細細的打磨,才能避免很多問題的產生。

相關文章