<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>

          99%的Java程序員會踩的6個坑

          共 9089字,需瀏覽 19分鐘

           ·

          2022-07-25 14:42

          點擊關(guān)注公眾號,Java干貨及時送達(dá)

          前言

          作為Java程序員的你,不知道有沒有踩過一些基礎(chǔ)知識的坑。

          有時候,某個bug,你查了半天,最后發(fā)現(xiàn)竟然是一個非常低級的錯誤。

          有時候,某些代碼,這一批數(shù)據(jù)功能正常,但換了一批數(shù)據(jù)就出現(xiàn)異常了。

          有時候,你可能會看著某行代碼目瞪口呆,心里想:這行代碼為什么會出錯?

          今天跟大家一起聊聊99%的Java程序員踩過,或者即將踩的6個坑。

          1. 用==號比較的坑

          不知道你在項目中有沒有見過,有些同事對Integer類型的兩個參數(shù)使用==號比較是否相等?

          反正我見過的,那么這種用法對嗎?

          我的回答是看具體場景,不能說一定對,或不對。

          有些狀態(tài)字段,比如:orderStatus有:-1(未下單),0(已下單),1(已支付),2(已完成),3(取消),5種狀態(tài)。

          這時如果用==判斷是否相等:

          Integer orderStatus1 = new Integer(1);
          Integer orderStatus2 = new Integer(1);
          System.out.println(orderStatus1 == orderStatus2);

          返回結(jié)果會是true嗎?

          答案:是false。

          有些同學(xué)可能會反駁,Integer中不是有范圍是:-128-127的緩存嗎?

          為什么是false?

          先看看Integer的構(gòu)造方法:

          它其實并沒有用到緩存。

          那么緩存是在哪里用的?

          答案在valueOf方法中:

          如果上面的判斷改成這樣:

          String orderStatus1 = new String("1");
          String orderStatus2 = new String("1");
          System.out.println(Integer.valueOf(orderStatus1) == Integer.valueOf(orderStatus2));

          返回結(jié)果會是true嗎?

          答案:還真是true。

          我們要養(yǎng)成良好編碼習(xí)慣,盡量少用==判斷兩個Integer類型數(shù)據(jù)是否相等,只有在上述非常特殊的場景下才相等。

          而應(yīng)該改成使用equals方法判斷:

          Integer orderStatus1 = new Integer(1);
          Integer orderStatus2 = new Integer(1);
          System.out.println(orderStatus1.equals(orderStatus2));

          運(yùn)行結(jié)果為true。

          2. Objects.equals的坑

          假設(shè)現(xiàn)在有這樣一個需求:判斷當(dāng)前登錄的用戶,如果是我們指定的系統(tǒng)管理員,則發(fā)送一封郵件。系統(tǒng)管理員沒有特殊的字段標(biāo)識,他的用戶id=888,在開發(fā)、測試、生產(chǎn)環(huán)境中該值都是一樣的。

          這個需求真的太容易實現(xiàn)了:

          UserInfo userInfo = CurrentUser.getUserInfo();

          if(Objects.isNull(userInfo)) {
             log.info("請先登錄");
             return;
          }

          if(Objects.equals(userInfo.getId(),888L)) {
             sendEmail(userInfo):
          }

          從當(dāng)前登錄用戶的上下文中獲取用戶信息,判斷一下,如果用戶信息為空,則直接返回。

          如果獲取到的用戶信息不為空,接下來判斷用戶id是否等于888。

          • 如果等于888,則發(fā)送郵件。
          • 如果不等于888,則啥事也不干。

          當(dāng)我們用id=888的系統(tǒng)管理員賬號登錄之后,做了相關(guān)操作,滿懷期待的準(zhǔn)備收郵件的時候,卻發(fā)現(xiàn)收了個寂寞。

          后來,發(fā)現(xiàn)UserInfo類是這樣定義的:

          @Data
          public class UserInfo {
              private Integer id;
              private String name;
              private Integer age;
              private String address;
          }

          此時,有些小伙伴可能會說:沒看出什么問題呀。

          但我要說的是這個代碼確實有問題。

          什么問題呢?

          下面我們重點看看它的equals方法:

          public static boolean equals(Object a, Object b) {
              return (a == b) || (a != null && a.equals(b));
          }

          equals方法的判斷邏輯如下:

          1. 該方法先判斷對象a和b的引用是否相等,如果相等則直接返回true。
          2. 如果引用不相等,則判斷a是否為空,如果a為空則返回false。
          3. 如果a不為空,調(diào)用對象的equals方法進(jìn)一步判斷值是否相等。

          這就要從Integerequals方法說起來了。

          它的equals方法具體代碼如下:

          public boolean equals(Object obj) {
              if (obj instanceof Integer) {
                  return value == ((Integer)obj).intValue();
              }
              return false;
          }

          先判斷參數(shù)obj是否是Integer類型,如果不是,則直接返回false。如果是Integer類型,再進(jìn)一步判斷int值是否相等。

          而上面這個例子中b是long類型,所以Integer的equals方法直接返回了false。

          也就是說,如果調(diào)用了Integer的equals方法,必須要求入?yún)⒁彩荌nteger類型,否則該方法會直接返回false。

          除此之外,還有Byte、Short、Double、Float、Boolean和Character也有類似的equals方法判斷邏輯。

          常見的坑有:

          1. Long類型和Integer類型比較,比如:用戶id的場景。
          2. Byte類型和Integer類型比較,比如:狀態(tài)判斷的場景。
          3. Double類型和Integer類型比較,比如:金額為0的判斷場景。

          3. BigDecimal的坑

          通常我們會把一些小數(shù)類型的字段(比如:金額),定義成BigDecimal,而不是Double,避免丟失精度問題。

          使用Double時可能會有這種場景:

          double amount1 = 0.02;
          double amount2 = 0.03;
          System.out.println(amount2 - amount1);

          正常情況下預(yù)計amount2 - amount1應(yīng)該等于0.01

          但是執(zhí)行結(jié)果,卻為:

          0.009999999999999998

          實際結(jié)果小于預(yù)計結(jié)果。

          Double類型的兩個參數(shù)相減會轉(zhuǎn)換成二進(jìn)制,因為Double有效位數(shù)為16位這就會出現(xiàn)存儲小數(shù)位數(shù)不夠的情況,這種情況下就會出現(xiàn)誤差。

          常識告訴我們使用BigDecimal能避免丟失精度。

          但是使用BigDecimal能避免丟失精度嗎?

          答案是否定的。

          為什么?

          BigDecimal amount1 = new BigDecimal(0.02);
          BigDecimal amount2 = new BigDecimal(0.03);
          System.out.println(amount2.subtract(amount1));

          這個例子中定義了兩個BigDecimal類型參數(shù),使用構(gòu)造函數(shù)初始化數(shù)據(jù),然后打印兩個參數(shù)相減后的值。

          結(jié)果:

          0.0099999999999999984734433411404097569175064563751220703125

          不科學(xué)呀,為啥還是丟失精度了?

          JdkBigDecimal構(gòu)造方法上有這樣一段描述:

          大致的意思是此構(gòu)造函數(shù)的結(jié)果可能不可預(yù)測,可能會出現(xiàn)創(chuàng)建時為0.1,但實際是0.1000000000000000055511151231257827021181583404541015625的情況。

          由此可見,使用BigDecimal構(gòu)造函數(shù)初始化對象,也會丟失精度。

          那么,如何才能不丟失精度呢?

          BigDecimal amount1 = new BigDecimal(Double.toString(0.02));
          BigDecimal amount2 = new BigDecimal(Double.toString(0.03));
          System.out.println(amount2.subtract(amount1));

          我們可以使用Double.toString方法,對double類型的小數(shù)進(jìn)行轉(zhuǎn)換,這樣能保證精度不丟失。

          其實,還有更好的辦法:

          BigDecimal amount1 = BigDecimal.valueOf(0.02);
          BigDecimal amount2 = BigDecimal.valueOf(0.03);
          System.out.println(amount2.subtract(amount1));

          使用BigDecimal.valueOf方法初始化BigDecimal類型參數(shù),也能保證精度不丟失。在新版的阿里巴巴開發(fā)手冊中,也推薦使用這種方式創(chuàng)建BigDecimal參數(shù)。

          4. Java8 filter的坑

          對于Java8中的Stream用法,大家肯定再熟悉不過了。

          我們通過對集合Stream操作,可以實現(xiàn):遍歷集合、過濾數(shù)據(jù)、排序、判斷、轉(zhuǎn)換集合等等,N多功能。

          這里重點說說數(shù)據(jù)的過濾。

          在沒有Java8之前,我們過濾數(shù)據(jù)一般是這樣做的:

          public List<User> filterUser(List<User> userList) {
              if(CollectionUtils.isEmpty(userList)) {
                  return Collections.emptyList();
              }
              
              List<User> resultList = Lists.newArrayList();
              for(User user: userList) {
                  if(user.getId() > 1000 && user.getAge() > 18)   {
                     resultList.add(user);
                  }
              }
              return resultList;
          }

          通常需要另一個集合輔助完成這個功能。

          但如果使用Java8的filter功能,代碼會變得簡潔很多,例如:

          public List<User> filterUser(List<User> userList) {
              if(CollectionUtils.isEmpty(userList)) {
                  return Collections.emptyList();
              }
              
              return userList.stream()
              .filter(user -> user.getId() > 1000 && user.getAge() > 18)
              .collect(Collectors.toList());
          }

          代碼簡化了很多,完美。

          但如果你對過濾后的數(shù)據(jù),做修改了:

          List<User> userList = queryUser();
          List<User> filterList = filterUser(userList);
          for(User user: filterList) {
             user.setName(user.getName() + "測試");
          }

          for(User user: userList) {
             System.out.println(user.getName());
          }

          你當(dāng)時可能只是想修改過濾后的數(shù)據(jù),但實際上,你會把元素數(shù)據(jù)一同修改了。

          意不意外,驚不驚喜?

          其根本原因是:過濾后的集合中,保存的是對象的引用,該引用只有一份數(shù)據(jù)。

          也就是說,只要有一個地方,把該引用對象的成員變量的值,做修改了,其他地方也會同步修改。

          如下圖所示:

          5. 自動拆箱的坑

          Java5之后,提供了自動裝箱自動拆箱的功能。

          自動裝箱是指:JDK會把基本類型,自動變成包裝類型。

          比如:

          Integer integer = 1;

          等價于:

          Integer integer = new Integer(1);

          而自動拆箱是指:JDK會把包裝類型,自動轉(zhuǎn)換成基本類型。

          例如:

          Integer integer = new Integer(2);
          int sum = integer + 5;

          等價于:

          Integer integer = new Integer(2);
          int sum = integer.intValue() + 5;

          但實際工作中,我們在使用自動拆箱時,往往忘記了判空,導(dǎo)致出現(xiàn)NullPointerException異常。

          5.1 運(yùn)算

          很多時候,我們需要對傳入的數(shù)據(jù)進(jìn)行計算,例如:

          public class Test2 {
              public static void main(String[] args) {
                  System.out.println(add(new Integer(1), new Integer(2)));
              }

              private static Integer add(Integer a, Integer b) {
                  return a + b;
              }
          }

          如果傳入了null值:

          System.out.println(add(nullnew Integer(2)));

          則會直接報錯。

          5.2 傳參

          有時候,我們定義的某個方法是基本類型,但實際上傳入了包裝類,比如:

          public static void main(String[] args) {
              Integer a = new Integer(1);
              Integer b = null;
              System.out.println(add(a, b));
          }

          private static Integer add(int a, int b) {
              return a + b;
          }

          如果出現(xiàn)add方法報NullPointerException異常,你可能會懵逼,int類型怎么會出現(xiàn)空指針異常呢?

          其實,這個問題出在:Integer類型的參數(shù),其實際傳入值為null,JDK字段拆箱,調(diào)用了它的intValue方法導(dǎo)致的問題。

          6. replace的坑

          很多時候我們在使用字符串時,想把字符串比如:ATYSDFA*Y中的字符A替換成字符B,第一個想到的可能是使用replace方法。

          如果想把所有的A都替換成B,很顯然可以用replaceAll方法,因為非常直觀,光從方法名就能猜出它的用途。

          那么問題來了:replace方法會替換所有匹配字符嗎?

          jdk的官方給出了答案。

          該方法會替換每一個匹配的字符串。

          既然replace和replaceAll都能替換所有匹配字符,那么他們有啥區(qū)別呢?

          replace有兩個重載的方法。

          • 其中一個方法的參數(shù):char oldChar 和 char newChar,支持字符的替換。
          source.replace('A''B')
          • 另一個方法的參數(shù)是:CharSequence target 和 CharSequence replacement,支持字符串的替換。
          source.replace("A""B")

          replaceAll方法的參數(shù)是:String regex 和 String replacement,即基于正則表達(dá)式的替換。

          例如對普通字符串進(jìn)行替換:

          source.replaceAll("A""B")

          使用正則表達(dá)替換(將*替換成C):

          source.replaceAll("\\*""C")

          順便說一下,將*替換成C使用replace方法也可以實現(xiàn):

          source.replace("*""C")

          小伙們看到看到二者的區(qū)別了沒?使用replace方法無需對特殊字符進(jìn)行轉(zhuǎn)義。

          不過,千萬注意,切勿使用如下寫法:

          source.replace("\\*""C")

          這種寫法會導(dǎo)致字符串無法替換。

          還有個小問題,如果我只想替換第一個匹配的字符串該怎么辦?

          這時可以使用replaceFirst方法:

          source.replaceFirst("A""B")

          說實話,這里內(nèi)容都很基礎(chǔ),但越基礎(chǔ)的東西,越容易大意失荊州,更容易踩坑。

          最后,統(tǒng)計一下,這些坑一個都沒踩過的同學(xué),麻煩舉個手。

            

          1、相比高人氣的Rust、Go,為何 Java、C 在工具層面進(jìn)展緩慢?

          2、讓程序員早點下班的《技術(shù)寫作指南》

          3、互聯(lián)網(wǎng)人為什么學(xué)不會擺爛

          4、為什么國外JetBrains做 IDE 就可以養(yǎng)活自己,國內(nèi)不行?區(qū)別在哪?

          5、微軟欲閉源VS Code的C#擴(kuò)展惹眾怒

          6、上能寫代碼,下要“揍”黑客,還有什么不是程序員的“鍋”?

          點在看

          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(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>
                  国产美女艹逼 | 亚洲精品永久久久久久 | 琪琪午夜成人久久电影网 | 欧美成人性做爱视频 | 91麻豆视频日播 |