Java的equals() 和 hashCode()

首先,equals()方法和hashCode()方法都來自於Object類的定義,Java類都繼承了這兩個方法,都定義了本身的實現。java

equals:

equlas()方法的正確理解應該是:判斷兩個對象是否相等。那麼判斷對象相等的標尺又是什麼?算法

在object類中,此標尺即爲==。ide

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

固然,這個標尺不是固定的,其餘類中能夠按照實際的須要對此標尺含義進行重定義。如String類中則是依據字符串內容是否相等來重定義了此標尺含義。性能

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;
    }

如此能夠增長類的功能性和實際編碼的靈活性。固然了,若是自定義的類沒有重寫equals()方法來從新定義此標尺,那麼默認的將是其父類的equals(),直到Object基類。ui

以下場景的實際業務需求,對於User bean,由實際的業務需求可知當屬性uid相同時,表示的是同一個User,即兩個User對象相等。則能夠重寫equals以重定義User對象相等的標尺。this

package com.corn.objectsummary;

public class User {

    private int uid;
    private String name;
    private int age;

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    protected String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof User)) {
            return false;
        }
        if (((User) obj).getUid() == this.getUid()) {
            return true;
        }
        return false;
    }
}
package com.corn.objectsummary;

public class ObjectTest implements Cloneable {

    public static void main(String[] args) {
        User u1 = new User();
        u1.setUid(111);
        u1.setName("張三");

        User u2 = new User();
        u2.setUid(111);
        u2.setName("張三丰");

        System.out.println(u1.equals(u2)); //返回true
    }

}

ObjectTest中打印出true,由於User類定義中重寫了equals()方法,這很好理解,極可能張三是一我的小名,張三丰纔是其大名,判斷這兩我的是否是同一我的,這時只用判斷uid是否相同便可。編碼

重寫equals()方法遵循的準則:spa

  • 對稱性:若是x.equals(y)返回是「true」,那麼y.equals(x)也應該返回是「true」。code

  • 反射性:x.equals(x)必須返回是「true」。對象

  • 類推性:若是x.equals(y)返回是「true」,並且y.equals(z)返回是「true」,那麼z.equals(x)也應該返回是「true」。

  • 還有一致性:若是x.equals(y)返回是「true」,只要x和y內容一直不變,無論你重複x.equals(y)多少次,返回都是「true」。

  • 任何狀況下,x.equals(null),永遠返回是「false」;x.equals(和x不一樣類型的對象)永遠返回是「false」。

如上重寫equals方法表面上看上去是能夠了,實則否則。由於它破壞了Java中的約定:重寫equals()方法必須重寫hasCode()方法。

hashCode:

hashCode():方法返回一個整形數值,表示該對象的哈希碼值。

hashCode()具備以下約定:

1).在Java應用程序程序執行期間,對於同一對象屢次調用hashCode()方法時,其返回的哈希碼是相同的,前提是將對象進行equals比較時所用的標尺信息未作修改。在Java應用程序的一次執行到另一次執行,同一對象的hashCode()返回的哈希碼無須保持一致;

2).若是兩個對象相等(依據:調用equals()方法),那麼這兩個對象調用hashCode()返回的哈希碼也必須相等;

3).反之,兩個對象調用hasCode()返回的哈希碼相等,這兩個對象不必定相等。

即嚴格的數學邏輯表示爲: 兩個對象相等 <=>  equals()相等  => hashCode()相等。所以,重寫equlas()方法必須重寫hashCode()方法,以保證此邏輯嚴格成立,同時能夠推理出:hasCode()不相等 => equals()不相等 <=> 兩個對象不相等。

可能有人在此產生疑問:既然比較兩個對象是否相等的惟一條件(也是次要條件)是equals,那麼爲何還要弄出一個hashCode(),而且進行如此約定,弄得這麼麻煩?

其實,這主要體如今hashCode()方法的做用上,其主要用於加強哈希表的性能。

以集合類中,以Set爲例,當新加一個對象時,須要判斷現有集合中是否已經存在與此對象相等的對象,若是沒有hashCode()方法,須要將Set進行一次遍歷,並逐一用equals()方法判斷兩個對象是否相等,此種算法時間複雜度爲o(n)。經過藉助於hasCode方法,先計算出即將新加入對象的哈希碼,而後根據哈希算法計算出此對象的位置,直接判斷此位置上是否已有對象便可。(注:Set的底層用的是Map的原理實現)

在此須要糾正一個理解上的誤區:對象的hashCode()返回的不是對象所在的物理內存地址。甚至也不必定是對象的邏輯地址,hashCode()相同的兩個對象,不必定相等,換言之,不相等的兩個對象,hashCode()返回的哈希碼可能相同。

所以,在上述代碼中,重寫了equals()方法後,須要重寫hashCode()方法。

package com.corn.objectsummary;

public class User {

    private int uid;
    private String name;
    private int age;

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    protected String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof User)) {
            return false;
        }
        if (((User) obj).getUid() == this.getUid()) {
            return true;
        }
        return false;
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + this.getUid();
        return result;
    }
}

注:上述hashCode()的重寫中出現了result*31,是由於result*31 = (result<<5) - result。之因此選擇31,是由於左移運算和減運算計算效率遠大於乘法運算。固然,也能夠選擇其餘數字。

相關文章
相關標籤/搜索