Java中對(duì)象比較的五種方式詳解
在 Java 中,對(duì)象比較 主要有以下 4–5 種核心方式,它們用途、語義、性能、適用場景完全不同。面試中常被追問的順序通常是:== → equals → hashCode → Comparable → Comparator。
下面按使用頻率和重要性逐一拆解。
1. == (引用比較 / 身份比較)
- 比較內(nèi)容:兩個(gè)引用變量是否指向堆中同一個(gè)對(duì)象(內(nèi)存地址是否相同)
- 適用類型:
- 基本類型 → 值比較(int、double、boolean 等)
- 引用類型 → 地址比較
- 默認(rèn)行為:Object 類的原始實(shí)現(xiàn)就是
return this == obj; - 特點(diǎn):最快,O(1),無方法調(diào)用開銷
- 典型場景:
- 判斷是否為同一個(gè)實(shí)例(單例模式、緩存命中)
- null 判斷
if (obj == null)
- 面試常問陷阱:
- String str1 = “hello”; String str2 = new String(“hello”);
→ str1 == str2 為 false(常量池 vs 堆)
- String str1 = “hello”; String str2 = new String(“hello”);
2. equals() (邏輯相等 / 值比較)
- 默認(rèn)行為(Object 類實(shí)現(xiàn)):
return (this == obj);→ 等價(jià)于 == - 重寫后語義:大多數(shù)業(yè)務(wù)類重寫后比較內(nèi)容是否相等(而非引用)
- 經(jīng)典重寫類(JDK 內(nèi)置已重寫):
| 類 | equals 比較內(nèi)容 | hashCode 是否同步重寫 |
|---|---|---|
| String | 字符序列是否相同 | 是 |
| Integer | 數(shù)值是否相同 | 是 |
| BigDecimal | 數(shù)值 + 精度是否相同 | 是 |
| LocalDate | 年月日是否相同 | 是 |
| Arrays | 內(nèi)容逐個(gè) equals(deepEquals 多維) | — |
- 重寫 equals 的正確姿勢(契約,必須遵守):
@Override
public boolean equals(Object o) {
// 1. 引用相同 → 相等
if (this == o) return true;
// 2. 類型檢查 + null 檢查
if (o == null || getClass() != o.getClass()) return false;
// 3. 強(qiáng)制轉(zhuǎn)換后逐字段比較(基本類型用 ==,對(duì)象用 equals)
Person other = (Person) o;
return age == other.age &&
Objects.equals(name, other.name) &&
Objects.equals(idCard, other.idCard);
}
- Objects.equals(a, b) 工具方法(強(qiáng)烈推薦):
- 內(nèi)部處理了 null 安全:
a == b || (a != null && a.equals(b))
- 內(nèi)部處理了 null 安全:
3. hashCode() (散列碼比較,常配合 equals 使用)
作用:為對(duì)象生成一個(gè) int 值,主要用于哈希表(HashMap、HashSet、Hashtable 等)
核心契約(Object 文檔原文):
- 同一個(gè)對(duì)象在生命周期內(nèi)多次調(diào)用 hashCode(),返回值必須相同(前提:equals 使用的字段沒變)
- 如果 a.equals(b) == true,則 a.hashCode() 必須等于 b.hashCode()
- 如果 a.equals(b) == false,不要求 hashCode 不同(但最好不同,以減少哈希沖突)
重寫 equals 必須重寫 hashCode 的原因:
- 違反第 2 條契約 → HashMap/HashSet 可能出現(xiàn)邏輯錯(cuò)誤(同一個(gè)對(duì)象在集合中出現(xiàn)多次,或 find 不到)
2025–2026 推薦生成方式(IDE 自動(dòng)生成或以下方式):
@Override
public int hashCode() {
return Objects.hash(name, age, idCard); // 最簡潔、最推薦(Java 7+)
}
- 性能提示:hashCode 分布越均勻越好,避免大量對(duì)象 hashCode 相同導(dǎo)致鏈表退化
4. Comparable 接口(自然排序)
- 包:java.lang
- 方法:
int compareTo(T o) - 返回值約定:
- 負(fù)數(shù) → this < o
- 0 → this == o(邏輯相等,不一定引用相等)
- 正數(shù) → this > o
- 典型實(shí)現(xiàn)(String、Integer、LocalDate 等都實(shí)現(xiàn)了):
public class Person implements Comparable<Person> {
private String name;
private int age;
@Override
public int compareTo(Person o) {
// 先按年齡升序,年齡相同再按姓名字典序
int ageCmp = Integer.compare(this.age, o.age);
if (ageCmp != 0) return ageCmp;
return this.name.compareTo(o.name);
}
}
- 使用場景:
- Collections.sort(list)
- TreeSet、TreeMap 的默認(rèn)排序
- Arrays.sort()(對(duì)象數(shù)組)
5. Comparator 接口(定制排序 / 外部比較器)
- 包:java.util
- 方法:
int compare(T o1, T o2) - 特點(diǎn):不侵入原類,可創(chuàng)建多個(gè)比較器
- Java 8+ 推薦寫法(最常用):
// 方式一:Lambda + comparing
List<Person> list = ...;
list.sort(Comparator.comparingInt(Person::getAge)
.thenComparing(Person::getName));
// 方式二:匿名內(nèi)部類(老項(xiàng)目常見)
Comparator<Person> byAgeThenName = (p1, p2) -> {
int cmp = Integer.compare(p1.getAge(), p2.getAge());
return cmp != 0 ? cmp : p1.getName().compareTo(p2.getName());
};
- 使用場景:
- 臨時(shí)排序需求
- TreeSet/TreeMap 指定比較器
- Stream.sorted(comparator)
6. 快速對(duì)比總結(jié)表(面試最愛畫的表)
| 比較方式 | 比較內(nèi)容 | 是否可重寫 | 是否侵入類 | 典型集合支持 | 性能 | 面試出現(xiàn)頻率 |
|---|---|---|---|---|---|---|
| == | 引用地址 | 否 | 否 | — | 極高 | ★★★★★ |
| equals() | 邏輯相等(內(nèi)容) | 是 | 是 | HashMap/HashSet | 高 | ★★★★★ |
| hashCode() | 散列值 | 是(必須與equals一致) | 是 | HashMap/HashSet | 極高 | ★★★★☆ |
| compareTo() | 自然排序大小 | 是 | 是 | TreeSet/TreeMap 默認(rèn) | 中 | ★★★★ |
| Comparator | 定制排序 | — | 否 | 任意集合/Stream | 中 | ★★★★ |
7. 常見面試連環(huán)追問(建議準(zhǔn)備的回答模板)
重寫了 equals 沒重寫 hashCode 會(huì)有什么問題?
→ HashMap 中 put 后 get 找不到;Set 中出現(xiàn)重復(fù)元素
String 為什么可以直接用 == 比較常量池字符串?
→ 字符串常量池 + intern() 機(jī)制
如何比較兩個(gè)對(duì)象是否“完全相等”(包括所有字段深比較)?
→ Apache Commons Lang → ReflectionToStringBuilder 或 Objects.deepEquals(多維數(shù)組)
Java 14+ Record 類自動(dòng)實(shí)現(xiàn)了哪些比較相關(guān)方法?
→ equals、hashCode、toString 全部按所有組件字段自動(dòng)生成
排序時(shí)如果 compareTo 返回 0,但 equals 返回 false 會(huì)怎樣?
→ TreeSet/TreeMap 會(huì)認(rèn)為相等而覆蓋(違反 SortedSet 語義)
希望這份總結(jié)能幫你快速建立 Java 對(duì)象比較的完整知識(shí)圖譜。
如果你正在準(zhǔn)備面試,想針對(duì)某一種方式(比如 equals + hashCode 的完整手寫實(shí)現(xiàn)、或 Comparator 的多種寫法)要更詳細(xì)的代碼示例,或者想看某個(gè)具體場景的對(duì)比代碼,直接告訴我,我可以繼續(xù)展開。
以上就是Java中對(duì)象比較的五種方式詳解的詳細(xì)內(nèi)容,更多關(guān)于Java對(duì)象比較方式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何在spring boot中進(jìn)行參數(shù)校驗(yàn)示例詳解
這篇文章主要介紹了如何在spring-boot中進(jìn)行參數(shù)校驗(yàn)及l(fā)ombok的使用詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
一文詳解java如何實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用
從?Java?8?開始,便引入了一種稱為“流式?API”的編程風(fēng)格,當(dāng)然也被稱為“鏈?zhǔn)皆O(shè)置”或“鏈?zhǔn)秸{(diào)用”,本文主要來和大家討論一下如何實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,感興趣的可以了解下2023-12-12
Java編程實(shí)現(xiàn)逆波蘭表達(dá)式代碼示例
本文主要對(duì)Java算法逆波蘭表達(dá)式的相關(guān)內(nèi)容作了介紹,涉及逆波蘭表達(dá)式的定義已經(jīng)在Java中的實(shí)現(xiàn),具有一定參考價(jià)值,需要的朋友可以了解下。2017-10-10
Java JDBC批量執(zhí)行executeBatch方法詳解
這篇文章主要介紹了Java JDBC批量執(zhí)行executeBatch方法詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08

