<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程序員會(huì)踩的6個(gè)坑

          共 9155字,需瀏覽 19分鐘

           ·

          2022-07-16 19:44

          前言

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

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

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

          有時(shí)候,你可能會(huì)看著某行代碼目瞪口呆,心里想:這行代碼為什么會(huì)出錯(cuò)?

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

          1. 用==號(hào)比較的坑

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

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

          我的回答是看具體場(chǎng)景,不能說一定對(duì),或不對(duì)。

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

          這時(shí)如果用==判斷是否相等:

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

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

          答案:是false。

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

          為什么是false?

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

          它其實(shí)并沒有用到緩存

          那么緩存是在哪里用的?

          答案在valueOf方法中:

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

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

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

          答案:還真是true。

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

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

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

          UserInfo userInfo = CurrentUser.getUserInfo();

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

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

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

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

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

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

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

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

          此時(shí),有些小伙伴可能會(huì)說:沒看出什么問題呀。

          但我要說的是這個(gè)代碼確實(shí)有問題。

          什么問題呢?

          下面我們重點(diǎn)看看它的equals方法:

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

          equals方法的判斷邏輯如下:

          1. 該方法先判斷對(duì)象a和b的引用是否相等,如果相等則直接返回true。
          2. 如果引用不相等,則判斷a是否為空,如果a為空則返回false。
          3. 如果a不為空,調(diào)用對(duì)象的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值是否相等。

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

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

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

          常見的坑有:

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

          如果你想進(jìn)一步了解Objects.equals方法的問題,可以看看我的另一篇文章《Objects.equals有坑》。

          3. BigDecimal的坑

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

          使用Double時(shí)可能會(huì)有這種場(chǎng)景:

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

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

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

          0.009999999999999998

          實(shí)際結(jié)果小于預(yù)計(jì)結(jié)果。

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

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

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

          答案是否定的。

          為什么?

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

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

          結(jié)果:

          0.0099999999999999984734433411404097569175064563751220703125

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

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

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

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

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

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

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

          其實(shí),還有更好的辦法:

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

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

          4. Java8 filter的坑

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

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

          這里重點(diǎ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;
          }

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

          但如果使用Java8的filter功能,代碼會(huì)變得簡(jiǎn)潔很多,例如:

          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());
          }

          代碼簡(jiǎn)化了很多,完美。

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

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

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

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

          意不意外,驚不驚喜?

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

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

          如下圖所示:

          5. 自動(dòng)拆箱的坑

          Java5之后,提供了自動(dòng)裝箱自動(dòng)拆箱的功能。

          自動(dòng)裝箱是指:JDK會(huì)把基本類型,自動(dòng)變成包裝類型。

          比如:

          Integer integer = 1;

          等價(jià)于:

          Integer integer = new Integer(1);

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

          例如:

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

          等價(jià)于:

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

          但實(shí)際工作中,我們?cè)谑褂米詣?dòng)拆箱時(shí),往往忘記了判空,導(dǎo)致出現(xiàn)NullPointerException異常。

          5.1 運(yùn)算

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

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

          則會(huì)直接報(bào)錯(cuò)。

          5.2 傳參

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

          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方法報(bào)NullPointerException異常,你可能會(huì)懵逼,int類型怎么會(huì)出現(xiàn)空指針異常呢?

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

          6. replace的坑

          很多時(shí)候我們?cè)谑褂米址畷r(shí),想把字符串比如:ATYSDFA*Y中的字符A替換成字符B,第一個(gè)想到的可能是使用replace方法。

          如果想把所有的A都替換成B,很顯然可以用replaceAll方法,因?yàn)榉浅V庇^,光從方法名就能猜出它的用途。

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

          jdk的官方給出了答案。

          該方法會(huì)替換每一個(gè)匹配的字符串。

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

          replace有兩個(gè)重載的方法。

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

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

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

          source.replaceAll("A""B")

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

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

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

          source.replace("*""C")

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

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

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

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

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

          這時(shí)可以使用replaceFirst方法:

          source.replaceFirst("A""B")

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

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

          程序汪資料鏈接

          程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

          Java項(xiàng)目分享  最新整理全集,找項(xiàng)目不累啦 07版

          堪稱神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階

          臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開放下載!

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

          字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開放下載!


          歡迎添加程序汪個(gè)人微信 itwang009  進(jìn)粉絲群或圍觀朋友圈


          瀏覽 39
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  高清视频在线观看一区 | 欧美性爱天天射 | 成人网大香蕉五月视频 | 色阁五月| 色四五月开心 |