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

