1. 程式人生 > >Java基礎系列-equals方法和hashCode方法

Java基礎系列-equals方法和hashCode方法

set等 rst 內存 ring 下標 this serial 需要 是不是

原創文章,轉載請標註出處:《Java基礎系列-equals方法和hashCode方法》

概述

equals方法和hashCode方法都是有Object類定義的。

public class Object {
    public native int hashCode();
    public boolean equals(Object obj) {
        return (this == obj);
    }
}

任何的類都是Object類的子類,所有它們默認都擁有這兩個方法。
equals方法用於定義兩個對象的比較方式,而hashCode方法是native方法,主要用戶計算對象的hash值。

equals

equals方法主要用於定義兩個對象的比較方式,默認的比較方式是比較內存地址,相對於基本類型來說就是值,而相對於引用類型來說就是堆中具體對象的地址。那麽就只有值相同的基本類型,和同一個對象的兩個引用才能相等。但是在我們實際業務系統中,兩個對象的相等一般指的是兩個對象的內容相同(邏輯相同),而不是說它兩個是同一個對象,這種情況使用默認的equals就無法實現相等(因為兩個不同對象地址值一定不同),這時候我們就需要對equals方法進行重寫,定義新的比較方式。

準則

  • 自省性:對於非null的x,存在:x.equals(x)返回true
  • 對稱性:對於非null的x和y,存在:x.equals(y)==y.equals(x)
  • 傳遞性:對於非null的x、y、z,存在:當x.equals(y)返回true,y.equals(z)返回true,則x.equals(z)一定為true
  • 一致性:對於非null的x和y,多次調用x.equals(y)所得的結果是不變的
  • 非空性:對於非null的x,存在x.equals(null)返回false

    重寫

    其實Java中已經為我們展示了如何重equals方法了,最經典的就是String的equals方法:
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    public boolean equals(Object anObject) {
        // 首先判斷兩個對象是不是同一個,地址相同否
        if (this == anObject) {
            return true;
        }
        // 判斷給定的對象是否是String類型,這裏instanceof關鍵字是重寫equals方法時經常使用的一個關鍵字
        // instanseof用於判斷右邊的類型是否是當前對象的類型或者超類型,超接口類型等
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            // 校驗兩個字符串的長度相同否
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                // 循環校驗兩個字符串中的每個字符是否相同
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
}

註意,使用instanceof在針對存在子類的情況下,可能會出現違反對稱性和傳遞性的情況,為了避免這種情況,可以通給getClass的方式比較類型。
自定義重寫:

public class EqualsTest {
    private int id;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Override
    public boolean equals(Object obj) {
        // 滿足非空性
        if(obj == null){
            return false;
        }
        // 滿足自省性
        if(this == obj){
            return true;
        }
        // 滿足對稱性、傳遞性、一致性
        if(this.getClass() == obj.getClass()
                && this.getClass().getClassLoader() == obj.getClass().getClassLoader()
                && this.id == ((EqualsTest)obj).getId()){
            return true;
        }
        return false;
    }
}

註意:這裏如果是有不同的類加載器加載的同一類的實例也是無法相等的。

hashCode

hashCode一般用於計算對象的hash值,它在類重寫equals的時候一起重寫,重寫它的目的是為了保證equals相同的兩個對象的hashCode結果一致,為什麽要保證這一點呢,那就歸結到java中的那幾個基於Hash實現的集合上了,比如HashMap、HashSet等,這些集合需要用到對象的hash值來參與計算定位。
使用hashCode的目的就是為了散列元素,最終元素能否散列均勻和hashCode的實現息息相關,即為hash函數。

實現方式

  • 鏈地址法(理解):在出現hash沖突的時候,在這個位置再插入新元素,並與原有元素形成一個鏈表,類似於HashMap的實現方式
  • 開放尋址法(了解):在出現hash沖突的時候,在當前位置的附近尋找空位來存放新元素,這種方式只需要一種數據結構,不需要引入新的數據結構。其實就是為每個hash結果準備一個探查序列,用來存放發生hash沖突的元素。
    • 線性探查法:當出現hash沖突,則在當前位置逐個向後尋找空位,將新元素保存到找到的第一個空位,當找到最後時,需要折返到一開頭繼續查找。由於探查序列固定,所以會引發一次集群問題。
    • 二次探查法:出現沖突,不再逐個順序探查,而是由某種函數計算的結果序列來探查,這個函數依賴於開始下標的平方,所以叫二次探查,開始下標的不同,序列就不相同,不同序列中會有重復的下標,由於每個下標開始的探查序列是固定的,所以會引發小規模集群,即二次集群問題。
    • 雙重散列法:要解決群集,就要想辦法讓相同hash結果的序列不同,最好讓序列函數依賴於元素本身,保證當元素不同時,即使hash結果一致,但一旦發生沖突,不同的元素的序列是不同的(因為序列還要依賴元素本身,元素不同,序列結果就會不同),這樣存在兩個依賴變量的探查方法,可以極大的避免集群問題。
  • 再HASH法(知道)
  • 建立公共溢出區法(知道)

hashCode的實現方式並不是隨手而來的,需要考慮各種情況,選擇合適的方式來實現,舉個例子,在Java的HashMap集合中,采用的就是鏈地址法來處理hash沖突。

參考:

  • Java hashCode() 和 equals()的若幹問題解答
  • 白話算法(6) 散列表(Hash Table)從理論到實用(上)
  • 白話算法(6) 散列表(Hash Table)從理論到實用(中)
  • 白話算法(6) 散列表(Hash Table)從理論到實用(下)

Java基礎系列-equals方法和hashCode方法