<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 6685字,需瀏覽 14分鐘

           ·

          2021-06-03 13:24

          前言


          JDK  作為我們每天必備的調(diào)用類庫,里面大量提供了基礎(chǔ)類供我們使用。可以說離開 JDK ,我們的 Java代碼寸步難行。 


          JDK 帶給我們的便利可謂是不勝枚舉,但同時這些方法在使用起來也存在一些坑,如果不注意就很容易掉入到陷阱里面,導(dǎo)致程序拋出錯誤。 


          JDK 中的很多方法都不會做非 null 判斷,可能設(shè)計 JDK 的作者默認(rèn)開發(fā)者已經(jīng)處理好 null 值了。不過這個設(shè)計可能會造成很嚴(yán)重的后果,實(shí)在是暗藏殺機(jī)。比如,今天早上我們查了一筆訂單沒有退款,查了一早上最終才發(fā)現(xiàn)是同事寫的代碼的 BigDecimal 的 subtract 方法的值沒有做非 nul判斷處理導(dǎo)致程序拋出了空指針異常。


          看似簡單的異常卻直接無法讓很多訂單退款,實(shí)在是小問題造成大事故。而要修補(bǔ)退款這個問題,要耗費(fèi)很多時間去修補(bǔ),出錯的成本太高。


          本文就來看看 JDK 中那些坑你沒商量的方法,這些方法很常見,相信你一定遇到過。


          1 String.valueOf() 方法的陷阱


          案發(fā)現(xiàn)場


          某個鳥語花香的早上,我們在開心的敲著代碼,突然客戶群有人投訴反映:我們發(fā)給用戶的短信有部分是尊敬的 "null" 你好,等等。


          開發(fā)第一時間看了代碼,覺得沒有問題啊。為什么短信內(nèi)容會出現(xiàn)用戶名為 null 呢?不是經(jīng)過了非空判斷的嗎?String.valueOf() 是 String 提供的一個類型轉(zhuǎn)換的方法,我們來看一下(代碼經(jīng)過了簡化):


          // 調(diào)用用戶服務(wù)根據(jù)用戶id獲取用戶信息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);}


          這段代碼是簡化過的,主要作用就是通過用戶服務(wù)根據(jù) id 獲取用戶信息發(fā)送短信。


          后來經(jīng)過定位發(fā)現(xiàn)了問題所在:首先,用戶的名字里有特殊的 emoji 符號。數(shù)據(jù)庫寫入的時候有部分寫入失敗。因?yàn)楫?dāng)時的數(shù)據(jù)庫字符格式并無法兼容 emoji,而獲取的時候因?yàn)檫@個問題值為 null 了。


          接下來是重點(diǎn): 


          public static String valueOf(Object obj) {    return (obj == null) ? "null" : obj.toString();}


          這里是重點(diǎn),也是最大的坑。


          注意:這里返回了一個 "null" 的字符串,而不是 null。這兩個是有很大區(qū)別的。當(dāng)進(jìn)行非空判斷的時候,返回的是 true。也就是這個 "null" 字符串是符合判空條件的!


          正確的姿勢是在 String.valueOf 方法前必須判空:


          if (userNameObject != null) {    String userName = userNameObject.toString();}


          2 Integer.parseInt() 方法很矯情


          事故現(xiàn)場


          業(yè)務(wù)場景為拉取訂單,打出訂單列表記錄。財務(wù)人員需要拉出對賬,結(jié)果總是發(fā)現(xiàn)很奇怪的一個現(xiàn)象:每次拉取少很多數(shù)據(jù)。好財務(wù)發(fā)現(xiàn)了,不然和第三方財務(wù)對賬就會虧很多錢。


          最終發(fā)現(xiàn),是訂單的一個字段值轉(zhuǎn) Integer 出錯了。那個訂單下的字段值是 120.0,通過 Integer.parseInt() 直接報錯了。恰好開發(fā)人員認(rèn)為這段開發(fā)肯定沒問題,因此就沒有 catch 異常。最后找了很久才發(fā)現(xiàn),因?yàn)樯婕暗降谌剑€讓別人查了半天……


          知道真相的我們都有點(diǎn)汗顏:這么丁點(diǎn)的錯誤排查了很久,實(shí)在是不應(yīng)該啊。


          Integer.parseInt() 方法用于將字符串轉(zhuǎn)化為 Integer 類型的方法。此方法的適用性就顯得比較窄,因?yàn)槭?String 類型的參數(shù)沒有任何限定,當(dāng)在傳入一些比如 50.0、20L、30d、40f 這類數(shù)據(jù)的情況下會拋出異常。


          我們來看一個例子:


          String input = "50.0";int out = Integer.parseInt(input);


          會拋出異常 NumberFormatException:



          事實(shí)上對于這樣的數(shù)據(jù),比如小數(shù)、float、double、long 類型數(shù)據(jù)都可以自動轉(zhuǎn)換,而不是給我們拋出煩人的報錯信息。如果預(yù)先知道是整數(shù)或者小數(shù),可以用 BigDecimal 轉(zhuǎn)換。


          注意:此方法不適用于 double、float、Long 類型的數(shù)據(jù),比如10d、20L。


          String input = "50.0";int out2 = new BigDecimal(input).intValue();System.out.println(out2);


          對于 float、long 類型的數(shù)據(jù)可以用以下方法來處理:


          推薦使用 hutool 的 NumberUtil.parseInt() 方法。充分考慮到了 float、double、long、小數(shù)等類型數(shù)據(jù)可能帶來的解析異常的問題。hutool 是一個國人開源的工具類庫,這里強(qiáng)烈推薦,容錯性和處理異常能力很強(qiáng)。


          3 BigDecimal 的除法坑你沒商量


          眾所周知,BigDecimal 是處理金額最有效的數(shù)據(jù)類型。一般進(jìn)行財務(wù)報表計算的時候?yàn)榱朔乐菇痤~出現(xiàn)錯誤,一般情況下都會采用 BigDecimal。而 double、float 都會存在些許的誤差。你開開心心地用 BigDecimal 進(jìn)行了計算,而最終的結(jié)果返回卻有問題。我們來看一個例子:


          BigDecimal ten = new BigDecimal(10);BigDecimal two= new BigDecimal(2);BigDecimal result = ten.divide(two);System.out.println(result.toString());


          常見的除法用起來沒有任何絲毫的問題,妥妥的沒毛病。但是一旦程序中的數(shù)據(jù)出現(xiàn)以下情況,如果用 BigDecimal 來接受前端的參數(shù)。而前端的參數(shù)是用戶輸入不確定的,一旦出現(xiàn)如下的數(shù)據(jù),我們來看看結(jié)果:


          BigDecimal ten = new BigDecimal(10);BigDecimal three= new BigDecimal(3);BigDecimal result = ten.divide(three);System.out.println(result.toString());


          執(zhí)行結(jié)果一看,居然報錯了:


           

          這就是 BigDecimal 的坑:一旦返回的結(jié)果是無限循環(huán)小數(shù),就會拋出 ArithmeticException。因此在進(jìn)行 BigDecimal 除法的時候,需要進(jìn)行保留小數(shù)的處理。


          正確的處理姿勢:


          BigDecimal ten = new BigDecimal(10);BigDecimal three= new BigDecimal(3);BigDecimal result = ten.divide(three, 2, BigDecimal.ROUND_HALF_UP);System.out.println(result.toString());


          4 Collections.emptyList() 此 List 非彼 List


          我們先來看一個例子:


          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。


          List<String> resultList = Collections.emptyList();resultList.add("test");


          結(jié)果拋出異常:

           


          原因是 Collections.emptyList 返回的并不是我們平時認(rèn)識的那個 List,它是一個內(nèi)部常量類:


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


          這個 List 并不具有 add、remove 元素的能力。我猜想是因?yàn)?JDK 設(shè)計之初的想法是將這個 List 作為一種只讀的 List ,并不提供數(shù)據(jù)的寫入能力。因此它僅可作為一種空值返回,無法進(jìn)行刪除、添加操作。


          5 List 可以一邊刪除一邊遍歷嗎?


          答案是肯定可以的。要不然的話 List 怎么刪除數(shù)據(jù)呢?不過要注意遍歷的姿勢,我們再來看一個簡單的例子:


          public static void main(String[] args) {    List<Integer> resultList = new ArrayList<>();    resultList.add(1);    resultList.add(2);    resultList.add(3);    for (Integer num : resultList) {        if (num == 1) {            resultList.remove(num);        }    }}

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



          仔細(xì)翻閱源碼會發(fā)現(xiàn),每次 remove 之前會檢查元素的條數(shù)。如果發(fā)現(xiàn)預(yù)期的 modCount 和當(dāng)前的 modCount 不一致就會拋出這個異常。modCount 是 List 中用來記錄修改次數(shù)的一個屬性,當(dāng)對元素進(jìn)行統(tǒng)計的時候就會對該元素加 1。而當(dāng)對 List 邊遍歷邊刪除的話,就會造成 excepted 與 modCount 不一致,從而拋出異常。


          final void checkForModification() {    if (modCount != expectedModCount) {        throw new ConcurrentModificationException();    }}


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


          List<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);list.add(4);Iterator<Integer> iterator = list.iterator();while (iterator.hasNext()) {    Integer integer = iterator.next();    if (integer == 2) {        iterator.remove();    }}


          6 總結(jié)


          JDK 的設(shè)計者有兩個很大的特點(diǎn):


          • 大多不會做非 null 判斷;

          • 出現(xiàn)錯誤直接 throw new Exception,容錯性很差。


          在實(shí)際開發(fā)中,面對 JDK 一定要謹(jǐn)慎使用。JDK 提供了便利的同時,也有一些我們使用上的盲區(qū)。應(yīng)該養(yǎng)成多看源碼,多注意錯誤性處理,防止在小問題上栽大跟頭。


          回到最開始說的那個 subtract 方法的問題,因?yàn)檫@個問題等需要我處理完之后用戶才能收到退款,這直接造成了用戶體驗(yàn)直線下降,而部分用戶還直接打電話投訴。同事一個小小的不謹(jǐn)慎和馬虎就給公司造成了很多負(fù)面影響,技術(shù)問題雖然不大但是帶來的業(yè)務(wù)影響范圍很嚴(yán)重。


          所以我們必須防微杜漸,小小的問題都得細(xì)細(xì)的打磨,才能避免很多問題的產(chǎn)生。


          7 持續(xù)更新


          7.1 BigDecimal 在比較的時候最好使用 compareTo 方法,不要使用 equals 方法


          如下案例。雖然 BigDecimal 重寫了 equals 方法,但是使用會存在問題:


          public static void main(String[] args) {    System.out.println(new BigDecimal("1").equals(new BigDecimal("1.0")));}


          D:\jdk\bin\java.exe ...false


          1 和 1.0 在比較的時候返回了 false。這是因?yàn)樵?equals 的源碼中進(jìn)行了數(shù)據(jù)的 scale(也就是精度)的比較。如果不一致就會返回 false。如果使用 compareTo 方法就不存在這個問題。


           7.2 MySQL 減法計算,如果有 null 值結(jié)果就為 null


          select 5-null  結(jié)果會返回 null。所以在進(jìn)行 MySQL 計算的時候,對于有可能出現(xiàn)  null 值的列一定要進(jìn)行 ifnull(field, 0) 的轉(zhuǎn)換,將 null 值轉(zhuǎn)化為 0。否則就會出現(xiàn)一些意想不到的數(shù)據(jù)錯誤和空指針問題。



          正確的姿勢:



          7.3 String 的 split 方法在進(jìn)行 || 分割的時候需要進(jìn)行轉(zhuǎn)義,否則結(jié)果會有問題


          String str = "77||88";final String[] split1 = str.split("||");final String[] split2 = str.split("\\|\\|");
          System.out.print("錯誤的分割方式:");for (String s : split1) {    System.out.print(s + " ");}System.out.print("\n");
          System.out.print("正確的分割方式:");for (String s : split2) { System.out.print(s + " ");}


          錯誤的分割方式:7 7 | | 8 8 |正確的分割方式:77 88

          轉(zhuǎn)自:Yrion,

          鏈接:cnblogs.com/wyq178/p/13520745.html

          瀏覽 40
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人人妻人人爽 | 国产午夜在线观看 | 欧美精品一区二区三区四区 | 69国产精品成人 | 免费国产福利 |