一文讀懂 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() 簡(jiǎn)化實(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)文章
springboot?vue接口測(cè)試定義編輯功能的實(shí)現(xiàn)
這篇文章主要為大家介紹了springboot?vue接口測(cè)試定義編輯功能的實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
eclipse配置tomcat10的詳細(xì)步驟總結(jié)
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識(shí),文章圍繞著eclipse配置tomcat10的詳細(xì)步驟展開,文中有非常詳細(xì)的介紹及圖文示例,需要的朋友可以參考下2021-06-06
重寫hashCode()和equals()方法詳細(xì)介紹
這篇文章主要介紹了重寫hashCode()和equals()方法詳細(xì)介紹,涉及重寫equals()方法,重寫hashCode()方法,重寫equals()而不重寫hashCode()的風(fēng)險(xiǎn)等相關(guān)內(nèi)容的介紹,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01
JFileChooser實(shí)現(xiàn)對(duì)選定文件夾內(nèi)圖片自動(dòng)播放和暫停播放實(shí)例代碼
這篇文章主要介紹了JFileChooser實(shí)現(xiàn)對(duì)選定文件夾內(nèi)圖片自動(dòng)播放和暫停播放實(shí)例代碼,需要的朋友可以參考下2017-04-04
Java處理InterruptedException異常的理論與實(shí)踐
在使用Java的過程中,有個(gè)情景或許很多人見過,您在編寫一個(gè)測(cè)試程序,程序需要暫停一段時(shí)間,于是調(diào)用 Thread.sleep()。但是編譯器或 IDE 報(bào)錯(cuò)說沒有處理檢查到的 InterruptedException。InterruptedException 是什么呢,為什么必須處理它?下面跟著小編一起來看看。2016-08-08
解析spring事務(wù)管理@Transactional為什么要添加rollbackFor=Exception.class
這篇文章主要介紹了spring事務(wù)管理@Transactional為什么要添加rollbackFor=Exception.class,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-11-11
Java中final、static關(guān)鍵字與方法的重寫和繼承易錯(cuò)點(diǎn)整理
這篇文章主要給大家介紹了關(guān)于Java中final、static關(guān)鍵字與方法的重寫和繼承易錯(cuò)點(diǎn)的相關(guān)資料,在Java編程中final關(guān)鍵字用于限制方法或類的進(jìn)一步修改,final方法不能被子類重寫,而static方法不可被重寫,只能被遮蔽,需要的朋友可以參考下2024-10-10

