新聞中心
前言
原本以為判斷結(jié)果會返回true的,但實際上返回了false。

成都創(chuàng)新互聯(lián)公司主要從事做網(wǎng)站、成都做網(wǎng)站、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)織金,十余年網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):028-86922220
記得很早之前,我使用Objects.equals方法也踩過類似的坑,所以有必要把這個問題記錄下來,分享給大家。
到底怎么回事呢?
1. 案發(fā)現(xiàn)場
假設(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(),888)) {
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 Long id;
private String name;
private Integer age;
private String address;
}
此時,有些小伙伴可能會說:沒看出什么問題呀。
但我要說的是這個代碼確實有問題。
什么問題呢?
答:UserInfo類的成員變量id=888是Long類型的,而Objects.equals方法右邊的888是int類型的,兩者不一致,導(dǎo)致返回的結(jié)果是false。
這算哪門子原因?
答:各位看官,別急,后面會細(xì)講的。
2. 判斷相等的方法
讓我們一起回顧一下,以前判斷兩個值是否相等的方法有哪些。
2.1 使用==號
之前判斷兩個值是否相等,最快的方法是使用==號。
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
不知道大家有沒有發(fā)現(xiàn),java中的基本類型,包含:int、long、short、byte、char、boolean、float、double這8種,可以使用==號判斷值是否相等。如果出現(xiàn)了基本類型的包裝類,比如:Integer,用一個基本類型和一個包裝類,使用==號也能正確判斷,返回true。
Integer和int比較時,會自動拆箱,這是比較值是否相等。
但如果有兩個包裝類,比如:d1和d2,使用==號判斷的結(jié)果可能是false。
兩個Integer比較時,比較的是它們指向的引用(即內(nèi)存地址)是否相等。
還有一個有意思的現(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é)果卻不相等。
小伙伴們,下巴驚掉了沒?
答:因為Integer有一個常量池,-128~127直接的Integer數(shù)據(jù)直接緩存進(jìn)入常量池。所以1在常量池,而128不在。
然而,new的Integer對象不適用常量池。從之前d1和d2例子的比較結(jié)果,就能看出這一點。
接下來,看看字符串的判斷:
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
普通的字符串變量,使用==號判斷,也能返回正確的結(jié)果。
但如果一個普通的字符串變量,跟new出來的字符串對象使用==號判斷時,返回false。這一點,跟之前說過的用一個基本類型和一個包裝類,使用==號判斷的結(jié)果有區(qū)別,字符串沒有自動拆箱的功能,這一點需要特別注意。
此外,兩個new出來的字符串對象使用==號判斷時,也返回false。
2.2 使用equals方法
使用上面的==號,可以非??焖倥袛?種基本數(shù)據(jù)類型是否相等,除此之外,還能判斷兩個對象的引用是否相等。
但現(xiàn)在有個問題,它無法判斷兩個對象在內(nèi)存中具體的數(shù)據(jù)值是否相等,比如:
String g = new String("abc");
String h = new String("abc");
System.out.println(g == h);
//結(jié)果:false字符串對象g和h是兩個不同的對象,它們使用==號判斷引用是否相等時,返回的是false。
那么,這種對象不同,但數(shù)據(jù)值相同的情況,我們該如何判斷相等呢?
答:使用equals方法。
equals方法其實是Object類中的方法:
public boolean equals(Object obj) {
return (this == obj);
}該方法非常簡單,只判斷兩個對象的引用是否相等。
很顯然,如果字符串類型直接使用父類(即Object類)的equals方法,去判斷對象不同,但值相同的情況,是有問題的。
所以,字符串(即String類)會重新的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;
}它依然會先判斷兩個對象引用是否相等,如果相等返回true。接下來,會把兩個字符串的挨個字符進(jìn)行比較,只有所有字符都相等才返回true。
nice,這樣就能解決g和h判斷的問題:
String e = "abc";
String f = "abc";
System.out.println(e.equals(f));
//結(jié)果:true
由此可見,我們使用String類重寫后的equals方法,判斷兩個字符串對象不同,但值相同時,會返回true。
3. 空指針異常
從前面我們已經(jīng)知道,判斷兩個對象是否相等,可以使用==號,或者equals方法。
但如果你更深入的使用它們,會發(fā)現(xiàn)一個問題,即:這兩種方式判斷值相等,都可能會報空指針異常。
先看看==號判斷的情況:
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使用==號判斷是否相等時,Integer會自動拆箱成int。
但由于c在自動拆箱的過程中,需要給它賦值int的默認(rèn)值0。而給空對象,賦值0,必然會報空指針異常。
接下來,看看equals方法:
String e = null;
String f = "abc";
System.out.println(e.equals(f));
//結(jié)果:NullPointerException
由于字符串對象e是空對象,直接調(diào)用它的equals方法時,就會報空指針異常。
那么,如何解決空指針問題呢?
答:在代碼中判空。
String e = null;
String f = "abc";
System.out.println(equals(e, f));
我們抽取了一個新的equals方法:
private static boolean equals(String e, String f) {
if (e == null) {
return f == null;
}
return e.equals(f);
}
該方法可以解決空指針問題,但有沒有辦法封裝一下,變得更通用一下,也適用于Integer或者其他類型的對象比較呢?
答:有辦法,繼續(xù)往下看。
4. Objects.equals的作用
Objects類位于java.util包下,它是里面提供了很多對象操作的輔助方法。
下面我們重點看看它的equals方法:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}equals方法的判斷邏輯如下:
- 該方法先判斷對象a和b的引用是否相等,如果相等則直接返回true。
- 如果引用不相等,則判斷a是否為空,如果a為空則返回false。
- 如果a不為空,調(diào)用對象的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方法比較兩個對象是否相等,確實可以避免空指針問題。
但這個有個疑問:前面使用a==b這種方式比較引用是否相等,當(dāng)時b為空時,程序直接拋了空指針異常。
而Objects.equals方法內(nèi)部也使用了a==b比較引用是否相等,為啥它沒有拋異常?
答:因為而Objects類的equals方法,使用了Object類型接收參數(shù),它的默認(rèn)值是null,不用進(jìn)行類型轉(zhuǎn)換,也不用像int類型對象賦值默認(rèn)值0。
從上面的理論可以看出,如果我們把代碼改成這樣,也不會拋異常:
int a = 1;
Integer c = null;
System.out.println(equals(a, c));
//結(jié)果:false
新定義了一個方法:
private static boolean equals(Object a, Object b) {
return a == b;
}執(zhí)行之后發(fā)現(xiàn),確實沒有拋空指針了。
所以O(shè)bjects.equals方法再比較兩個對象是否相等時,確實是一個不錯的方法。
但它有坑,不信繼續(xù)往下看。
5. Objects.equals的坑
各位小伙們看到這里,可能有點心急了,到底是什么坑?
廢話不多說,直接上例子:
Integer a = 1;
long b = 1L;
System.out.println(Objects.equals(a, b));
//結(jié)果:false
什么?返回結(jié)果是false?
而如果你直接用==號判斷:
Integer a = 1;
long b = 1L;
System.out.println(a == b);
//結(jié)果:true
返回又是true。
a和b明明都是1,為什么使用Objects.equals方法判斷不相等呢?
這就要從Integer的equals方法說起來了。
它的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。
原來坑在這里!!!
其實,如果把代碼改成這樣:
Integer a = 1;
long b = 1L;
System.out.println(Objects.equals(b, a));
//結(jié)果:false
執(zhí)行結(jié)果也是false。
因為Long的equals方法代碼,跟之前Integer的類似:
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}也是判斷入?yún)?,如果不是Long類型,則該方法直接返回false。
除此之外,還有Byte、Short、Double、Float、Boolean和Character也有類似的equals方法判斷邏輯。
由此可見,我們在使用Objects.equals方法,判斷兩個值是否相等時,一定要保證兩個入?yún)⒌念愋鸵恢?。否則即使兩個值相同,但其結(jié)果仍然會返回false,這是一個大坑。
那么,如何解決上面的問題呢?
可以將參數(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
有些情況也可以直接用==號判斷:
Integer a = 1;
long b = 1L;
System.out.println(a==b);
//結(jié)果:true
除了Objects.equals方法在兩個入?yún)㈩愋筒煌?,而會直接返回false之外,java的8種基本類型包裝類的equals也會有相同的問題,需要小伙們特別注意。
之前,如果直接使用java基本類型包裝類的equals方法判斷相等。
Integer a = new Integer(1);
long b = 1L;
System.out.println(a.equals(b));
在idea中,如果你將鼠標(biāo)放在equals方法上,會出現(xiàn)下面的提示:
這時你就知道方法用錯了,趕緊修正。但如果直接用包裝類的equals方法,有個問題就是可能存在報空指針異常的風(fēng)險。
如果你使用Objects.equals方法判斷相等,在idea中就并沒有錯誤提示。
除此之外,我還測試了findBug、sonar等工具,Objects.equals方法兩個參數(shù)類型不一致的問題,也沒有標(biāo)識出來。
小伙們趕緊看看你們的代碼,踩坑了沒?
常見的坑有:
- Long類型和Integer類型比較,比如:用戶id的場景。
- Byte類型和Integer類型比較,比如:狀態(tài)判斷的場景。
- Double類型和Integer類型比較,比如:金額為0的判斷場景。
文章標(biāo)題:小心!Objects.equals有坑
網(wǎng)站路徑:http://www.fisionsoft.com.cn/article/dhphipe.html


咨詢
建站咨詢
