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

          Objects.equals 有坑!

          共 7184字,需瀏覽 15分鐘

           ·

          2022-04-16 13:27

          朋友最近 review 別人代碼的時(shí)候,發(fā)現(xiàn)有個(gè)同事,在某個(gè)業(yè)務(wù)場(chǎng)景下,使用Objects.equals方法判斷兩個(gè)值相等時(shí),返回了跟預(yù)期不一致的結(jié)果,引起了我的興趣。

          原本以為判斷結(jié)果會(huì)返回 true 的,但實(shí)際上返回了 false!

          記得很早之前,自己使用Objects.equals方法也踩過(guò)類(lèi)似的坑,所以有必要把這個(gè)問(wèn)題記錄下來(lái),分享給大家。

          到底怎么回事呢?

          1. 案發(fā)現(xiàn)場(chǎng)

          假設(shè)現(xiàn)在有這樣一個(gè)需求:判斷當(dāng)前登錄的用戶(hù),如果是我們指定的系統(tǒng)管理員,則發(fā)送一封郵件。系統(tǒng)管理員沒(méi)有特殊的字段標(biāo)識(shí),他的用戶(hù)id=888,在開(kāi)發(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(),888))?{
          ???sendEmail(userInfo):
          }

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

          如果獲取到的用戶(hù)信息不為空,接下來(lái)判斷用戶(hù)id是否等于888。

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

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

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

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

          此時(shí),有些小伙伴可能會(huì)說(shuō):沒(méi)看出什么問(wèn)題呀。

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

          什么問(wèn)題呢?

          答:UserInfo類(lèi)的成員變量id=888是Long類(lèi)型的,而Objects.equals方法右邊的888是int類(lèi)型的,兩者不一致,導(dǎo)致返回的結(jié)果是false。

          這算哪門(mén)子原因?

          答:各位看官,別急,后面會(huì)細(xì)講的。

          2. 判斷相等的方法

          讓我們一起回顧一下,以前判斷兩個(gè)值是否相等的方法有哪些。

          2.1 使用==號(hào)

          之前判斷兩個(gè)值是否相等,最快的方法是使用==號(hào)。

          int?a?=?1;
          int?b?=?1;
          byte?c?=?1;
          Integer?d1?=?new?Integer(1);
          Integer?d2?=?new?Integer(1);
          System.out.println(a?==?b);?
          //結(jié)果:true
          System.out.println(a?==?c);?
          //結(jié)果:true
          System.out.println(a?==?d1);?
          //結(jié)果:true
          System.out.println(d2?==?a);?
          //結(jié)果:true
          System.out.println(d1?==?d2);?
          //結(jié)果:false

          不知道大家有沒(méi)有發(fā)現(xiàn),java中的基本類(lèi)型,包含:int、long、short、byte、char、boolean、float、double這8種,可以使用==號(hào)判斷值是否相等。如果出現(xiàn)了基本類(lèi)型的包裝類(lèi),比如:Integer,用一個(gè)基本類(lèi)型和一個(gè)包裝類(lèi),使用==號(hào)也能正確判斷,返回true。

          Integer和int比較時(shí),會(huì)自動(dòng)拆箱,這是比較值是否相等。

          但如果有兩個(gè)包裝類(lèi),比如:d1和d2,使用==號(hào)判斷的結(jié)果可能是false。

          兩個(gè)Integer比較時(shí),比較的是它們指向的引用(即內(nèi)存地址)是否相等。

          還有一個(gè)有意思的現(xiàn)象:

          Integer?d3?=?1;
          Integer?d4?=?1;
          Integer?d5?=?128;
          Integer?d6?=?128;
          System.out.println(d3?==?d4);?
          //結(jié)果:true
          System.out.println(d5?==?d6);?
          //結(jié)果:false

          都是給Integer類(lèi)型的參數(shù),直接賦值后進(jìn)行比較。d3和d4判斷的結(jié)果相等,但d5和d6判斷的結(jié)果卻不相等。

          小伙伴們,下巴驚掉了沒(méi)?

          答:因?yàn)镮nteger有一個(gè)常量池,-128~127直接的Integer數(shù)據(jù)直接緩存進(jìn)入常量池。所以1在常量池,而128不在。

          然而,new的Integer對(duì)象不適用常量池。從之前d1和d2例子的比較結(jié)果,就能看出這一點(diǎn)。

          接下來(lái),看看字符串的判斷:

          String?e?=?"abc";
          String?f?=?"abc";
          String?g?=?new?String("abc");
          String?h?=?new?String("abc");
          System.out.println(e?==?f);?
          //結(jié)果:true
          System.out.println(e?==?g);?
          //結(jié)果:false
          System.out.println(g?==?h);?
          //結(jié)果:false

          普通的字符串變量,使用==號(hào)判斷,也能返回正確的結(jié)果。

          但如果一個(gè)普通的字符串變量,跟new出來(lái)的字符串對(duì)象使用==號(hào)判斷時(shí),返回false。這一點(diǎn),跟之前說(shuō)過(guò)的用一個(gè)基本類(lèi)型和一個(gè)包裝類(lèi),使用==號(hào)判斷的結(jié)果有區(qū)別,字符串沒(méi)有自動(dòng)拆箱的功能,這一點(diǎn)需要特別注意。

          此外,兩個(gè)new出來(lái)的字符串對(duì)象使用==號(hào)判斷時(shí),也返回false。

          2.2 使用equals方法

          使用上面的==號(hào),可以非常快速判斷8種基本數(shù)據(jù)類(lèi)型是否相等,除此之外,還能判斷兩個(gè)對(duì)象的引用是否相等。

          但現(xiàn)在有個(gè)問(wèn)題,它無(wú)法判斷兩個(gè)對(duì)象在內(nèi)存中具體的數(shù)據(jù)值是否相等,比如:

          String?g?=?new?String("abc");
          String?h?=?new?String("abc");
          System.out.println(g?==?h);?
          //結(jié)果:false

          字符串對(duì)象g和h是兩個(gè)不同的對(duì)象,它們使用==號(hào)判斷引用是否相等時(shí),返回的是false。

          那么,這種對(duì)象不同,但數(shù)據(jù)值相同的情況,我們?cè)撊绾闻袛嘞嗟饶兀?/p>

          答:使用equals方法。

          equals方法其實(shí)是Object類(lèi)中的方法:

          public?boolean?equals(Object?obj)?{
          ????return?(this?==?obj);
          }

          該方法非常簡(jiǎn)單,只判斷兩個(gè)對(duì)象的引用是否相等。

          很顯然,如果字符串類(lèi)型直接使用父類(lèi)(即Object類(lèi))的equals方法,去判斷對(duì)象不同,但值相同的情況,是有問(wèn)題的。

          所以,字符串(即String類(lèi))會(huì)重新的equals方法:

          public?boolean?equals(Object?anObject)?{
          ????if?(this?==?anObject)?{
          ????????return?true;
          ????}
          ????if?(anObject?instanceof?String)?{
          ????????String?anotherString?=?(String)anObject;
          ????????int?n?=?value.length;
          ????????if?(n?==?anotherString.value.length)?{
          ????????????char?v1[]?=?value;
          ????????????char?v2[]?=?anotherString.value;
          ????????????int?i?=?0;
          ????????????while?(n--?!=?0)?{
          ????????????????if?(v1[i]?!=?v2[i])
          ????????????????????return?false;
          ????????????????i++;
          ????????????}
          ????????????return?true;
          ????????}
          ????}
          ????return?false;
          }

          它依然會(huì)先判斷兩個(gè)對(duì)象引用是否相等,如果相等返回true。接下來(lái),會(huì)把兩個(gè)字符串的挨個(gè)字符進(jìn)行比較,只有所有字符都相等才返回true。

          nice,這樣就能解決g和h判斷的問(wèn)題:

          String?e?=?"abc";
          String?f?=?"abc";
          System.out.println(e.equals(f));?
          //結(jié)果:true

          由此可見(jiàn),我們使用String類(lèi)重寫(xiě)后的equals方法,判斷兩個(gè)字符串對(duì)象不同,但值相同時(shí),會(huì)返回true。

          3. 空指針異常

          從前面我們已經(jīng)知道,判斷兩個(gè)對(duì)象是否相等,可以使用==號(hào),或者equals方法。

          但如果你更深入的使用它們,會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題,即:這兩種方式判斷值相等,都可能會(huì)報(bào)空指針異常

          先看看==號(hào)判斷的情況:

          int?a?=?1;
          Integer?b?=?new?Integer(1);
          Integer?c?=?null;
          System.out.println(a?==?b);?
          //結(jié)果:true
          System.out.println(a?==?c);?
          //結(jié)果:NullPointerException

          int和Integer使用==號(hào)判斷是否相等時(shí),Integer會(huì)自動(dòng)拆箱成int。

          但由于c在自動(dòng)拆箱的過(guò)程中,需要給它賦值int的默認(rèn)值0。而給空對(duì)象,賦值0,必然會(huì)報(bào)空指針異常。

          接下來(lái),看看equals方法:

          String?e?=?null;
          String?f?=?"abc";
          System.out.println(e.equals(f));?
          //結(jié)果:NullPointerException

          由于字符串對(duì)象e是空對(duì)象,直接調(diào)用它的equals方法時(shí),就會(huì)報(bào)空指針異常。

          那么,如何解決空指針問(wèn)題呢?

          答:在代碼中判空。

          String?e?=?null;
          String?f?=?"abc";
          System.out.println(equals(e,?f));

          我們抽取了一個(gè)新的equals方法:

          private?static?boolean?equals(String?e,?String?f)?{
          ????if?(e?==?null)?{
          ????????return?f?==?null;
          ????}
          ????return?e.equals(f);
          }

          該方法可以解決空指針問(wèn)題,但有沒(méi)有辦法封裝一下,變得更通用一下,也適用于Integer或者其他類(lèi)型的對(duì)象比較呢?

          答:有辦法,繼續(xù)往下看。

          4. Objects.equals的作用

          Objects類(lèi)位于java.util包下,它是里面提供了很多對(duì)象操作的輔助方法。

          下面我們重點(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)一步判斷值是否相等。

          該方法是如何使用的?

          int?a?=?1;
          int?b?=?1;
          Integer?c?=?null;

          System.out.println(Objects.equals(a,?c));?
          //結(jié)果:false
          System.out.println(Objects.equals(c,?a));?
          //結(jié)果:false
          System.out.println(Objects.equals(a,?b));?
          //結(jié)果:true

          從上面的列子看出,使用Objects.equals方法比較兩個(gè)對(duì)象是否相等,確實(shí)可以避免空指針問(wèn)題。

          但這個(gè)有個(gè)疑問(wèn):前面使用a==b這種方式比較引用是否相等,當(dāng)時(shí)b為空時(shí),程序直接拋了空指針異常。

          而Objects.equals方法內(nèi)部也使用了a==b比較引用是否相等,為啥它沒(méi)有拋異常?

          答:因?yàn)槎?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">Objects類(lèi)的equals方法,使用了Object類(lèi)型接收參數(shù),它的默認(rèn)值是null,不用進(jìn)行類(lèi)型轉(zhuǎn)換,也不用像int類(lèi)型對(duì)象賦值默認(rèn)值0。

          從上面的理論可以看出,如果我們把代碼改成這樣,也不會(huì)拋異常:

          int?a?=?1;
          Integer?c?=?null;
          System.out.println(equals(a,?c));
          //結(jié)果:false

          新定義了一個(gè)方法:

          private?static?boolean?equals(Object?a,?Object?b)?{
          ????return?a?==?b;
          }

          執(zhí)行之后發(fā)現(xiàn),確實(shí)沒(méi)有拋空指針了。

          所以O(shè)bjects.equals方法再比較兩個(gè)對(duì)象是否相等時(shí),確實(shí)是一個(gè)不錯(cuò)的方法。

          但它有坑,不信繼續(xù)往下看。

          5. Objects.equals的坑

          各位小伙們看到這里,可能有點(diǎn)心急了,到底是什么坑?

          廢話(huà)不多說(shuō),直接上例子:

          Integer?a?=?1;
          long?b?=?1L;
          System.out.println(Objects.equals(a,?b));
          //結(jié)果:false

          什么?返回結(jié)果是false?

          而如果你直接用==號(hào)判斷:

          Integer?a?=?1;
          long?b?=?1L;
          System.out.println(a?==?b);
          //結(jié)果:true

          返回又是true。

          a和b明明都是1,為什么使用Objects.equals方法判斷不相等呢?

          這就要從Integerequals方法說(shuō)起來(lái)了。

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

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

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

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

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

          原來(lái)坑在這里!!!

          其實(shí),如果把代碼改成這樣:

          Integer?a?=?1;
          long?b?=?1L;
          System.out.println(Objects.equals(b,?a));
          //結(jié)果:false

          執(zhí)行結(jié)果也是false。

          因?yàn)長(zhǎng)ong的equals方法代碼,跟之前Integer的類(lèi)似:

          public?boolean?equals(Object?obj)?{
          ????if?(obj?instanceof?Long)?{
          ????????return?value?==?((Long)obj).longValue();
          ????}
          ????return?false;
          }

          也是判斷入?yún)ⅲ绻皇荓ong類(lèi)型,則該方法直接返回false。

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

          由此可見(jiàn),我們?cè)谑褂肙bjects.equals方法,判斷兩個(gè)值是否相等時(shí),一定要保證兩個(gè)入?yún)⒌念?lèi)型要一致。否則即使兩個(gè)值相同,但其結(jié)果仍然會(huì)返回false,這是一個(gè)大坑。

          那么,如何解決上面的問(wèn)題呢?

          可以將參數(shù)b的類(lèi)型強(qiáng)制轉(zhuǎn)換成int。

          Integer?a?=?1;
          long?b?=?1L;
          System.out.println(Objects.equals(a,?(int)b));
          //結(jié)果:true

          或者將參數(shù)a的類(lèi)型強(qiáng)制轉(zhuǎn)換成long。

          Integer?a?=?1;
          long?b?=?1L;
          System.out.println(Objects.equals(b,?(long)a));
          //結(jié)果:true

          有些情況也可以直接用==號(hào)判斷:

          Integer?a?=?1;
          long?b?=?1L;
          System.out.println(a==b);
          //結(jié)果:true

          除了Objects.equals方法在兩個(gè)入?yún)㈩?lèi)型不同,而會(huì)直接返回false之外,java的8種基本類(lèi)型包裝類(lèi)的equals也會(huì)有相同的問(wèn)題,需要小伙們特別注意。

          之前,如果直接使用java基本類(lèi)型包裝類(lèi)的equals方法判斷相等。

          Integer?a?=?new?Integer(1);
          long?b?=?1L;
          System.out.println(a.equals(b));

          在idea中,如果你將鼠標(biāo)放在equals方法上,會(huì)出現(xiàn)下面的提示:

          這時(shí)你就知道方法用錯(cuò)了,趕緊修正。但如果直接用包裝類(lèi)的equals方法,有個(gè)問(wèn)題就是可能存在報(bào)空指針異常的風(fēng)險(xiǎn)。

          如果你使用Objects.equals方法判斷相等,在idea中就并沒(méi)有錯(cuò)誤提示。

          除此之外,我還測(cè)試了findBug、sonar等工具,Objects.equals方法兩個(gè)參數(shù)類(lèi)型不一致的問(wèn)題,也沒(méi)有標(biāo)識(shí)出來(lái)。

          小伙們趕緊看看你們的代碼,踩坑了沒(méi)?

          常見(jiàn)的坑有:

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



          往期推薦

          做了個(gè)找打的網(wǎng)站,拿去玩!

          字節(jié)跳動(dòng)最?lèi)?ài)考的 64 道算法題(JS版)

          比 ping 強(qiáng)百倍的工具!結(jié)果一目了然

          5000 人,炸了!

          全棧初中生,牛皮!

          瀏覽 24
          點(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>
                  蜜桃无码高清 | 我要看免费 A片 | 国产艹逼网站 | 日韩国产在线一区 | 亚洲免费视频网 |