天天看點

Java常見面試問題: equals()與hashCode()的使用

本篇文章有如下方面: ① equals()與‘==’的差別; ② equals()方法的重寫規則(5條); ③ 為什麼重寫equals()的同時還需要重寫hashCode(); ④ JDK 7中對hashCode()方法的改進; ⑤ Java API文檔中關于hashCode()方法的規定; ⑥ 重寫equals()方法時推薦使用getClass(), 而不是instanceof; ⑦編寫一個完美的equals()方法的建議.

目錄

  • 1 equals()與‘==’的差別
  • 2 equals()方法的重寫規則
  • 3 為什麼重寫equals()的同時還需要重寫hashCode()
  • 4 JDK 7中對hashCode()方法的改進
  • 5 Java API文檔中關于hashCode()方法的規定
  • 6 重寫equals()方法時推薦使用getClass(), 而不是instanceof
  • 7 編寫一個完美的equals()方法的建議
  • 參考資料
  • 版權聲明

預設情況下也就是從超類Object繼承而來的equals()方法與‘==’是完全等價的, 比較的都是對象的記憶體位址.

但我們可以重寫equals()方法, 使其按照我們的需求的方式進行比較, 比如String類就重寫了equals方法, 它比較的是字元的序列, 而不再是記憶體位址.

自反性、對稱性、傳遞性等都是 <集合論> 中的概念.

(1) 自反性: 對于任何非null的引用值x, x.equals(x)應傳回true.

(2) 對稱性: 對于任何非null的引用值x與y, 當且僅當:y.equals(x)傳回true時, x.equals(y)才傳回true.

(3) 傳遞性: 對于任何非null的引用值x、y與z, 如果y.equals(x)傳回true, y.equals(z)傳回true, 那麼x.equals(z)也應傳回true.

(4) 一緻性: 對于任何非null的引用值x與y, 假設對象上equals比較中的資訊沒有被修改, 則多次調用x.equals(y)始終傳回true或者始終傳回false.

(5) 對于任何非空引用值x, x.equal(null)應傳回false.

這個問題主要和映射(Map接口)相關. 我們知道Map接口的類會使用到鍵(Key)的哈希碼, 當我們調用

put()/get()

方法操作Map容器時, 都是根據Key的哈希碼來計算存儲位置的, 是以如果我們對哈希碼的擷取沒有相關保證, 就可能會得不到預期的結果.

在Java中, 我們可以通過

hashCode()

方法擷取對象的哈希碼, 哈希碼的值就是對象的存儲位址, 這個方法在Object類中聲明, 是以所有的子類都含有該方法.

hashCode就是哈希碼(或者散列碼), 是由對象導出的一個整型值, 哈希碼是沒有規律的, 如果x與y是兩個不同的對象, 那麼x.hashCode()與y.hashCode()就不會相同 —— 隻要x和y對象沒有重寫hashCode()方法, JVM規範中明确要求它們的散列碼不會相同.

String(字元串)的哈希碼是由字元串的内容導出的, 也就是String類中重寫了hashCode()方法.

(1) Java釋出者希望我們使用更加安全的調用方式來傳回散列碼, 也就是使用null安全的

java.util.Objects.hashCode()

方法, 這個方法的優點是如果參數為null, 就隻傳回0, 否則傳回對象參數調用的hashCode的結果.

Objects.hashCode()的源碼如下:
public static int hashCode(Object o) {
    return o != null ? o.hashCode() : 0;
}
           

(2) JDK 7中還提供了一個方法:

java.util.Objects.hash(Object... objects)

, 當需要組合多個散列值時可以調用該方法, 比如:

public class Model {
    private String name;
    private double salary;
    private int sex;
    // @Override
    // public int hashCode() {
    //     return Objects.hashCode(name) + new Double(salary).hashCode() + new Integer(sex).hashCode();
    // }

    @Override
    public int hashCode() {
        return Objects.hash(name, salary, sex);
    }
}
           

擴充: 如果我們提供的是一個數值類型的變量, 那麼我們可以調用

Arrays.hashCode()

方法來計算它的散列碼, 這個散列碼是由數組中各個元素的散列碼組成的.

—— 内容摘自《Java深入解析》.

(1) 在Java應用程式執行期間, 如果在equals()方法中涉及到的資訊沒有被修改, 那麼在同一個對象上多次調用hashCode()方法時必須一緻地傳回相同的整數. 如果多次執行同一個應用程式時, 不要求該整數必須相同.

(2) 如果兩個對象通過調用equals()方法是相等的, 那麼這兩個對象調用hashCode()方法必須傳回相同的整數.

(3) 如果兩個對象通過調用equals()方法是不相等的, 不要求這兩個對象調用hashCode()方法必須傳回不同的整數. 但是開發人員應該意識到: 對不同的對象産生不同的hash值可以提高哈希表的性能.

在重寫

equals()

方法時, 一般推薦使用

getClass()

來進行類型判斷, 而不是使用

instanceof

關鍵字.

除非所有的子類有統一的語義才使用

instanceof

, 統一的語義就是說, 不同的子類在

equals()

方法中比較的内容相同.

我們知道,

instanceof

關鍵字的作用是判斷其左邊對象是否為其右邊類型的執行個體, 傳回boolean類型的資料, 它多用來判斷繼承關系中的某個子類的執行個體是否為父類的實作.

—— 摘自《Java核心技術 第一卷:基礎知識》.

(1) 顯式參數命名為otherObject, 稍後需要将它轉換成另一個叫做other的變量 (參數名命名, 強制轉換請參考下一條建議);

(2) 将otherObject轉換為相應的類類型變量:

ClassName other = (ClassName) otherObject;

;

(3) 檢測this與otherObject是否引用同一個對象:

if(this == otherObject) return true;

—— 存儲位址相同, 肯定是同個對象, 直接傳回true;

(4) 檢測otherObject是否為null , 如果為null, 傳回false:

if(otherObject == null) return false;

(5) 比較this與otherObject是否屬于同一個類(視需求而選擇):

① 如果equals的語義(可以了解為equals比較的内容)在每個子類中有所改變, 就使用getClass檢測:

if(getClass() != otherObject.getClass()) return false;

② 如果所有的子類都擁有統一的語義(比較的内容不變), 就使用instanceof檢測:

if(!(otherObject instanceof ClassName)) return false;

(6) 對所有需要比較的域進行比較: 使用==比較基本類型域, 使用equals比較對象域. 如果所有的域都比對, 就傳回true, 否則就傳回flase:

① 如果在子類中重新定義equals()方法, 就要在其中包含調用

super.equals(other)

② 當此方法被重寫時, 通常有必要重寫 hashCode() 方法, 以維護 hashCode 方法的正常協定, 該協定聲明 相等對象必須具有相等的哈希碼

重寫equal()時為什麼也得重寫hashCode()之深度解讀equal方法與hashCode方法淵源

作者: 馬瘦風

出處: 部落格園 馬瘦風的部落格

感謝閱讀, 如果文章有幫助或啟發到你, 點個[好文要頂👆] 或 [推薦👍] 吧😜

本文版權歸部落客所有, 歡迎轉載, 但 [必須在文章頁面明顯位置給出原文連結], 否則部落客保留追究相關人員法律責任的權利.