Object類中的.equals()方法和.hashCode()方法詳解

Object類中的.equals()方法和.hashCode()方法詳解

一、判斷相等

​ 衆所周知,在java中判斷兩個變量或者對象是否相等,可以使用 == 操作符或者‘.equals()’方法。而.equals()方法又涉及到.hashCode()方法,他們究竟的原理是什麼,讓我們細細道來。

基本類型的相等判斷

​ 當判斷基本類型的相等與否時,用‘==‘判斷完全沒問題,因爲基本類型的相等與否完全取決於它的,在《java編程思想》一書中寫道:“關係操作符生成的是一個boolean結果,它們計算的是操作數的值之間的關係”

​ 這看似簡單的一句話其實告訴我們,用**==**這種基本操作去判斷相等是一種基本操作,並沒有“源碼”,比如如下的代碼:

import static java.lang.System.out;
public class Main {

    /**
     * @param args
     */
    public static void main(String[] args) {
        
        int n=3;
        int m=3;
        
        out.println(n==m);
        
        String str = new String("hello");
        String str1 = new String("hello");
        String str2 = new String("hello");
        
        out.println(str1==str2);
        
        str1 = str;
        str2 = str;
        out.println(str1==str2);
    }

}

​ 會輸出 true,false,true

​ 結果很簡單,最後一次判斷讓str1和str2指向了同一對象,所以他們就相等了。

總結== 操作符對於基本類型來說對比的是他們的值,而對於引用類型,對比的是他們的地址

對象類型的相等判斷

​ 對於對象類型的相等判斷,相信大家都知道用.equals()方法。

​ Object類的.equals()方法長下面這個樣子:

    public boolean equals(Object obj) {
        return (this == obj);
    }

​ 所以很顯然,我們如果不重寫Object類的.equals()方法,那麼得到的結果和直接用 == 沒什麼區別!

二、重寫.equals()方法

​ 我們先來看一個成功的範例:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        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;
    }

​ 以上是final的String類中重寫的.equals()方法,它相當於先調用了super.equals()方法也就是直接用==判斷,之後再確認了傳進來的對象是字符串後,將其強制轉換爲字符串類型,再分割爲char數組,進行逐位判斷它的ASCII碼是否相等。

​ 我們自己定義類的.equals()方法寫法:

1	@Override
2	public boolean equals(Object obj) {
3		if (super.equals(obj)) {
4			return true;
5		}
6		if (obj == null) {
7			return false;
8		}
9		if (getClass() != obj.getClass()) {
10			return false;
11		}
12		/*
13		 * Model1 mol = (Model1)obj;
14			return (mol.code == code);
15		 */
16		return (obj.hashCode() == hashCode());
17	}

解讀:

​ 1.@override 標籤是非常建議加上的,方法重寫的原則是方法名,返回值,以及參數個數和參數類型要完全和父類方法一致,否則你寫的方法將不會覆蓋掉父類的任何方法。然而如果你犯了這些錯誤,在你寫了@override 標籤後編譯器將會報錯。

​ 2.第3-5行調用了Object類的.equals()方法,當然你也可以寫成上面那樣。

​ 3.第6-11行,根據java語言規範要求,.equals()方法需要具有以下的特性:

1.自反性:對於任何非空引用x,x.equals(x)應當返回true。
2.對稱性:對於任何引用x和y,當且僅當x.equals(y)返回true,y.equals(x)也應當返回true。
3.傳遞性:對於任何引用x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也應該返回true。
4.一致性:如果x和y引用的對象沒有發生變化,反覆調用x.equals(y)應該返回同樣的結果。
5.對於任意非空引用x,x.equals(null)應該返回false。

​ 所以在涉及繼承關係的兩個類去進行equals判斷時,第9行的

getClass() != obj.getClass()

​ 不能換成

obj instanceof 'ClassName'

​ 因爲其不滿足對稱性。

​ 4.在第13行,意味着你已經確認了obj對象就是我們這個類的類型,所以可以通過強制類型轉換將其轉換回來,使用==比較基本類型域,使用.equals()方法比較對象域。

​ 注:在實現邏輯時,如果該類中有多個域將涉及到相等判斷時,可以這麼寫:

return field1 == other.field1
	&& field2.equals(other.field2)
	&& field3.equals(other.field3)
	...

​ 不過更好的方法Objects.equals()方法是null安全的,寫成如下:

return field1 == other.field1
	&& Objects.equals(field2,other.field2)
	&& Objects.equals(field3,other.field3)
	...

​ 5.這裏的第16行是第13到15行的另一種寫法,因爲java開發規範還規定了:Equals與HashCode的定義必須一致,如果x.equals(y)返回true,那麼x.hashCode()就必須與y.hashCode()具有相同的值。

三、hashCode與.hashCode()方法

​ 先來介紹一下hashcode是個什麼東東?

​ 首先,每個對象都有hashcode,Object類中的hashCode()方法得到的就是這個對象的hashCode,同時也是這個對象的存儲地址。

​ 當我們重寫了.hashCode()方法時,並不意味着我們改變了他們的存儲地址,只是改變了返回的值(int)

String類的hashCode()

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

​ 以上是String類重寫的hashCode()方法,它是根據字符串中每個字符的ASCII碼計算得到的,所以:

String s = "Ok";
String t = new String("Ok");

​ 這兩個字符串的hashCode是相等的,79 * 31 + 107 = 2556。

重寫hashCode方法

@override
public int hashCode(){
    rerturn 7 * name.hashCode()
        + 11 * new Double(salary).hashCode()
        + 13 * hireDay.hashCode();
}

更好的方法,將採用null安全的Objects.hashCode()方法,以及使用Double.hashCode()避免創建Double對象。

@override
public int hashCode(){
    rerturn 7 * Objects.hashCode(name)
        + 11 * Double.hashCode(salary)
        + 13 * Objects.hashCode(hireDay);
}

更簡潔的方法:

public int hashCode(){
    rerturn Objects.hash(name,salary,hireDay)
}

Objects.hash()的底層實現:

public static int hash(Object... values) {
    return Arrays.hashCode(values);
}
public static int hashCode(Object a[]) {
    if (a == null)
        return 0;
        
    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

四、參考資料

[1](美)凱S.霍斯特曼,(Cay S.Horstmann)著,周立新,陳波,葉乃文,鄺勁筠,杜永萍譯.java核心技術 卷1 基礎知識[M].機械工業出版社,2018年第10版,166-172頁.

[2] 海子的博客,https://www.cnblogs.com/dolphin0520/p/3592500.html

[3] 最好的資料就是源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章