一文讀懂 Java 中的 ==、equals () 與 hashCode ()原理與避坑指南
在 Java 開發(fā)中,==、equals() 和 hashCode() 是處理對象比較和哈希計算的核心元素,理解它們之間的區(qū)別與聯(lián)系對編寫高質(zhì)量代碼至關(guān)重要。
一、== 運算符
== 是 Java 中的比較運算符,用于比較兩個值是否相等,其行為取決于比較的是基本類型還是引用類型:
1. 比較基本數(shù)據(jù)類型
對于 int、double、char 等基本類型,== 比較的是實際存儲的值:
int a = 10; int b = 10; System.out.println(a == b); // true,值相等 double c = 3.14; double d = 3.14; System.out.println(c == d); // true
2. 比較引用數(shù)據(jù)類型
對于對象(引用類型),== 比較的是對象在內(nèi)存中的地址(即是否為同一個對象):
因為java是值傳遞,這里可能是地址的副本進(jìn)行比較,根據(jù)這個副本也可以修改對象。
但是,這種比較不關(guān)心變量是否相同,只關(guān)心引用的對象是否相同。
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false,兩個不同的對象,地址不同
String s3 = s1;
System.out.println(s1 == s3); // true,指向同一個對象注意點
- 基本類型的包裝類(如
Integer、Double)使用==時,同樣比較地址而非值(除非觸發(fā)常量池緩存機制) - 基本數(shù)據(jù)類型不能與null比較,因為基本數(shù)據(jù)類型就沒有null這個值。
null == null結(jié)果為true,任何對象與null用==比較都為false
(1)基本類型 vs 包裝類(==比較)
- 包裝類會自動拆箱為基本類型,比較的是值。
例:int a = 5; Integer b = 5; System.out.println(a == b); // true
(2)包裝類 vs 包裝類(==比較)
- 比較的是對象的引用地址(是否為同一個對象),而非值。
- 注意:Java 對
Integer在[-128, 127]范圍內(nèi)有緩存機制,超出此范圍會創(chuàng)建新對象。
例 1:Integer a = 100; Integer b = 100; System.out.println(a == b); // true(使用緩存)
例 2:Integer a = 200; Integer b = 200; System.out.println(a == b); // false(新對象) - 當(dāng)其中一個為new Integer(1)時,是強制顯式創(chuàng)建一個對象,這里會開辟一個新的對象,即使緩存中有也不會使用。這時候比較就是false
二、equals () 方法
equals() 是 Object 類定義的實例方法,用于判斷兩個對象是否 "相等",默認(rèn)行為與 == 一致,比較的是對象的內(nèi)存地址(即是否為同一個對象)。
1. 默認(rèn)實現(xiàn)(Object 類中)
public boolean equals(Object obj) {
return (this == obj); // 本質(zhì)就是用 == 比較地址
}2. 重寫后的常見實現(xiàn)
多數(shù)類會重寫 equals() 方法,使其比較對象的內(nèi)容而非地址,例如 String 類:
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.equals(s2)); // true,內(nèi)容相同
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.equals(list2)); // true,兩個空列表內(nèi)容相同3. 重寫 equals () 的規(guī)范
重寫 equals() 時需遵循以下規(guī)則(來自《Effective Java》):
- 自反性:
x.equals(x)必須返回true - 對稱性:若
x.equals(y)為true,則y.equals(x)也必須為true - 傳遞性:若
x.equals(y)和y.equals(z)為true,則x.equals(z)也必須為true - 一致性:多次調(diào)用
x.equals(y)應(yīng)返回相同結(jié)果(前提是對象未被修改) - 非空性:
x.equals(null)必須返回false
4. 重寫示例(自定義類)
public class User {
private String id;
private String name;
// 構(gòu)造方法、getter、setter 省略
@Override
public boolean equals(Object o) {
// 1. 自身判斷
if (this == o) return true;
// 2. 類型判斷
if (o == null || getClass() != o.getClass()) return false;
// 3. 內(nèi)容比較
User user = (User) o;
return Objects.equals(id, user.id) &&
Objects.equals(name, user.name);
}
}注意點
- 若調(diào)用者是
null(如null.equals(obj)):必拋空指針異常。 - 若參數(shù)是
null(如obj.equals(null)):返回false(因為null不是任何對象的實例)。 - 如果兩個對象通過
equals()比較返回true,那么它們的hashCode()必須返回相同的值;反之,hashCode()不同的對象,equals()必須返回false。
三、hashCode () 方法
hashCode() 也是 Object 類的方法,返回一個 int 類型的哈希值,主要用于哈希表(如 HashMap、HashSet)中快速定位對象。
1. 基本作用
- 哈希值用于確定對象在哈希表中的存儲位置
- 提高哈希表的查找效率(理想情況下,不同對象應(yīng)具有不同哈希值)
2. 默認(rèn)實現(xiàn)
Object 類的 hashCode() 返回對象的內(nèi)存地址轉(zhuǎn)換后的整數(shù)(不同 JVM 實現(xiàn)可能不同)。
3. 重寫原則
關(guān)鍵規(guī)則:如果兩個對象通過 equals() 比較相等,則它們的 hashCode() 必須返回相同的值。反之則不成立(不同對象也可能有相同哈希值,即哈希沖突)。
這是因為哈希表在判斷對象是否存在時,會先通過哈希值定位,再用 equals() 精確比較。若違反此規(guī)則,會導(dǎo)致哈希表無法正常工作:
// 反例:equals相等但hashCode不同
class BadExample {
private int value;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BadExample that = (BadExample) o;
return value == that.value;
}
@Override
public int hashCode() {
return (int) (Math.random() * 1000); // 錯誤實現(xiàn):相同對象可能返回不同哈希值
}
}4. 正確的重寫實現(xiàn)
通常結(jié)合對象中參與 equals() 比較的字段來計算哈希值:
@Override
public int hashCode() {
// 使用 Objects.hash() 簡化實現(xiàn)
return Objects.hash(id, name);
}
// 等價于手動計算
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}使用 31 是因為它是一個質(zhì)數(shù),能減少哈希沖突,且 31 * i 可以被優(yōu)化為 (i << 5) - i,提高計算效率。
四、三者之間的關(guān)系總結(jié)
==與equals():- 未重寫
equals()時,兩者功能一致(比較地址) - 重寫
equals()后,==仍比較地址,equals()比較內(nèi)容
- 未重寫
equals()與hashCode():- 核心約定:
equals()為 true →hashCode()必須相等 - 反之不成立:
hashCode()相等 →equals()不一定為 true(哈希沖突) - 實際應(yīng)用:在哈希集合中,先通過
hashCode()定位,再用equals()確認(rèn)
- 核心約定:
使用場景:
- 比較基本類型 → 用
== - 比較對象地址 → 用
== - 比較對象內(nèi)容 → 用
equals() - 自定義類用于哈希表 → 必須同時重寫
equals()和hashCode()
- 比較基本類型 → 用
五、常見面試題解析
- 為什么重寫 equals () 必須重寫 hashCode ()?
- 答:為了保證希表(如 哈HashMap)的正確性。如果兩個對象 equals 相等但 hashCode 不同,會導(dǎo)致它們在哈希表中被存儲在不同位置,從而出現(xiàn) "相同對象卻被視為不同" 的情況。
- String 類的 == 和 equals () 有什么區(qū)別?
- 答:
==比較對象地址,equals()比較字符串內(nèi)容。由于字符串常量池的存在,"abc" == "abc"為 true,但new String("abc") == new String("abc")為 false。
- 答:
- Integer 類型用 == 比較時要注意什么?
- 答:Integer 對 -128~127 范圍的值有緩存,因此
Integer a = 100; Integer b = 100; a == b為 true,但超出此范圍則為 false,應(yīng)始終用equals()比較值。
- 答:Integer 對 -128~127 范圍的值有緩存,因此
到此這篇關(guān)于一文讀懂 Java 中的 ==、equals () 與 hashCode ()原理與避坑指南的文章就介紹到這了,更多相關(guān)java ==、equals () 與 hashCode ()內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud服務(wù)接口調(diào)用OpenFeign及使用詳解
這篇文章主要介紹了SpringCloud服務(wù)接口調(diào)用——OpenFeign,在學(xué)習(xí)Ribbon時,服務(wù)間調(diào)用使用的是RestTemplate+Ribbon實現(xiàn),而Feign在此基礎(chǔ)上繼續(xù)進(jìn)行了封裝,使服務(wù)間調(diào)用變得更加方便,需要的朋友可以參考下2023-04-04
Java abstract class 與 interface對比
這篇文章主要介紹了 Java abstract class 與 interface對比的相關(guān)資料,需要的朋友可以參考下2016-12-12
詳解Springboot應(yīng)用中設(shè)置Cookie的SameSite屬性
Chrome 51 開始,瀏覽器的 Cookie 新增加了一個SameSite屬性,用來防止 CSRF 攻擊和用戶追蹤。今天通過本文給大家介紹Springboot應(yīng)用中設(shè)置Cookie的SameSite屬性,感興趣的朋友一起看看吧2022-01-01
spring-boot-maven-plugin插件爆紅問題及解決方案
這篇文章主要介紹了spring-boot-maven-plugin插件爆紅問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-05-05
Java?List<JSONObject>中的數(shù)據(jù)如何轉(zhuǎn)換為List<T>
這篇文章主要介紹了Java?List<JSONObject>中的數(shù)據(jù)如何轉(zhuǎn)換為List<T>問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-05-05
Java Semaphore實現(xiàn)高并發(fā)場景下的流量控制
在java開發(fā)的工作中是否會出現(xiàn)這樣的場景,你需要實現(xiàn)一些異步運行的任務(wù),該任務(wù)可能存在消耗大量內(nèi)存的情況,所以需要對任務(wù)進(jìn)行并發(fā)控制。本文將介紹通過Semaphore類優(yōu)雅的實現(xiàn)并發(fā)控制,感興趣的可以了解一下2021-12-12

