欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中Objects.equals踩坑記錄

 更新時(shí)間:2022年03月22日 09:46:26   作者:蘇三說(shuō)技術(shù)  
最近在工作中發(fā)現(xiàn)一個(gè)問(wèn)題,覺(jué)著還是挺有必要記錄下的,這篇文章主要給大家介紹了關(guān)于Java中Objects.equals踩坑的相關(guān)資料,需要的朋友可以參考下

前言

最近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ò)類似的坑,所以有必要把這個(gè)問(wèn)題記錄下來(lái),分享給大家。

到底怎么回事呢?

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

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

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

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

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

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

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

@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類的成員變量id=888是Long類型的,而Objects.equals方法右邊的888是int類型的,兩者不一致,導(dǎo)致返回的結(jié)果是false。

這算哪門子原因?

答:各位看官,別急,后面會(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中的基本類型,包含:int、long、short、byte、char、boolean、float、double這8種,可以使用號(hào)判斷值是否相等。如果出現(xiàn)了基本類型的包裝類,比如:Integer,用一個(gè)基本類型和一個(gè)包裝類,使用號(hào)也能正確判斷,返回true。

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

但如果有兩個(gè)包裝類,比如: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類型的參數(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è)基本類型和一個(gè)包裝類,使用號(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ù)類型是否相等,除此之外,還能判斷兩個(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類中的方法:

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

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

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

所以,字符串(即String類)會(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

由此可見,我們使用String類重寫后的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或者其他類型的對(duì)象比較呢?

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

4. Objects.equals的作用

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

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

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

equals方法的判斷邏輯如下:

  • 該方法先判斷對(duì)象a和b的引用是否相等,如果相等則直接返回true。
  • 如果引用不相等,則判斷a是否為空,如果a為空則返回false。
  • 如果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)槎鳲bjects類的equals方法,使用了Object類型接收參數(shù),它的默認(rèn)值是null,不用進(jìn)行類型轉(zhuǎn)換,也不用像int類型對(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)心急了,到底是什么坑?

廢話不多說(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方法判斷不相等呢?

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

它的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。

也就是說(shuō),如果調(diào)用了Integer的equals方法,必須要求入?yún)⒁彩荌nteger類型,否則該方法會(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的類似:

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

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

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

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

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

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

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

或者將參數(shù)a的類型強(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)㈩愋筒煌?,而?huì)直接返回false之外,java的8種基本類型包裝類的equals也會(huì)有相同的問(wèn)題,需要小伙們特別注意。

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

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

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

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

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

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

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

常見的坑有:

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

總結(jié)

到此這篇關(guān)于Java中Objects.equals踩坑記錄的文章就介紹到這了,更多相關(guān)Java Objects.equals踩坑內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • AntDesign封裝全局異常處理全局?jǐn)r截器

    AntDesign封裝全局異常處理全局?jǐn)r截器

    這篇文章主要為大家介紹了AntDesign封裝全局異常處理全局?jǐn)r截器,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • JAVA?module-info.java文件詳解

    JAVA?module-info.java文件詳解

    這篇文章主要介紹了JAVA?module-info.java文件詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • string boot 與 自定義interceptor的實(shí)例講解

    string boot 與 自定義interceptor的實(shí)例講解

    下面小編就為大家分享一篇string boot 與 自定義interceptor的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • Mybatis基于注解形式的sql語(yǔ)句生成實(shí)例代碼

    Mybatis基于注解形式的sql語(yǔ)句生成實(shí)例代碼

    這篇文章主要介紹了 Mybatis基于注解形式的sql語(yǔ)句生成實(shí)例代碼,需要的朋友可以參考下
    2017-09-09
  • MyBatis批量插入幾千條數(shù)據(jù)為何慎用foreach

    MyBatis批量插入幾千條數(shù)據(jù)為何慎用foreach

    這篇文章主要介紹了MyBatis批量插入幾千條數(shù)據(jù)為何慎用foreach問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • IntelliJ IDEA 2023.2正式發(fā)布新UI和Profiler轉(zhuǎn)正(最新推薦)

    IntelliJ IDEA 2023.2正式發(fā)布新UI和Profiler轉(zhuǎn)正(最新推薦)

    北京時(shí)間2023年7月26日,IntelliJ IDEA 2023.2正式發(fā)布,IntelliJ IDEA 2023.2 引入 AI Assistant(AI助手),通過(guò)一組由 AI 提供支持的功能助力開發(fā),今天給大家分享IntelliJ IDEA 2023.2正式發(fā)布新UI和Profiler轉(zhuǎn)正,感興趣的朋友一起看看吧
    2023-10-10
  • SpringBoot集成EasyExcel的步驟

    SpringBoot集成EasyExcel的步驟

    EasyExcel是阿里巴巴開源poi插件之一,主要解決了poi框架使用復(fù)雜,sax解析模式不容易操作,數(shù)據(jù)量大起來(lái)容易OOM,解決了POI并發(fā)造成的報(bào)錯(cuò)。主要解決方式:通過(guò)解壓文件的方式加載,一行一行的加載,并且拋棄樣式字體等不重要的數(shù)據(jù),降低內(nèi)存的占用。
    2021-06-06
  • springboot整合通用Mapper簡(jiǎn)化單表操作詳解

    springboot整合通用Mapper簡(jiǎn)化單表操作詳解

    這篇文章主要介紹了springboot整合通用Mapper簡(jiǎn)化單表操作,通用Mapper是一個(gè)基于Mybatis,將單表的增刪改查通過(guò)通用方法實(shí)現(xiàn),來(lái)減少SQL編寫的開源框架,需要的朋友可以參考下
    2019-06-06
  • Java版畫板的實(shí)現(xiàn)方法

    Java版畫板的實(shí)現(xiàn)方法

    這篇文章主要為大家詳細(xì)介紹了Java版畫板的實(shí)現(xiàn)方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • JavaMe開發(fā)繪制文本框TextEdit

    JavaMe開發(fā)繪制文本框TextEdit

    在JavaMe連載(3)-也說(shuō)MVC設(shè)計(jì)模式 一文中提到了一個(gè)TextEdit類,但沒(méi)有給出具體實(shí)現(xiàn),TextEdit是采用GameCanvas繪制的文本編輯器。本文結(jié)合實(shí)例給出實(shí)現(xiàn)的方法。
    2015-09-09

最新評(píng)論