天天看點

重寫equals()方法就必須重寫hashCode()方法

       最近看了Object類的源碼,對hashCode() 和equals()方法有了更深的認識。重寫equals()方法就必須重寫hashCode()方法的原因,需要從源頭Object類講起。

public native int hashCode();

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

hashCode()方法是一個本地native方法,傳回的是對象引用中存儲的對象的記憶體位址,而equals方法是利用==來比較的也是對象的記憶體位址。從上邊我們可以看出,hashCode方法和equals方法是一緻的。還有最關鍵的一點,我們來看Object類中關于hashCode()方法的注釋:

/** 
 * Returns a hash code value for the object. This method is 
 * supported for the benefit of hash tables such as those provided by 
 * {@link java.util.HashMap}. 
 * <p> 
 * The general contract of {@code hashCode} is: 
 * <ul> 
 * <li>Whenever it is invoked on the same object more than once during 
 *     an execution of a Java application, the {@code hashCode} method 
 *     must consistently return the same integer, provided no information 
 *     used in {@code equals} comparisons on the object is modified. 
 *     This integer need not remain consistent from one execution of an 
 *     application to another execution of the same application. 
 * <li>If two objects are equal according to the {@code equals(Object)} 
 *     method, then calling the {@code hashCode} method on each of 
 *     the two objects must produce the same integer result. 
 * <li>It is <em>not</em> required that if two objects are unequal 
 *     according to the {@link java.lang.Object#equals(java.lang.Object)} 
 *     method, then calling the {@code hashCode} method on each of the 
 *     two objects must produce distinct integer results.  However, the 
 *     programmer should be aware that producing distinct integer results 
 *     for unequal objects may improve the performance of hash tables. 
 * </ul> 
 * <p> 
 * As much as is reasonably practical, the hashCode method defined by 
 * class {@code Object} does return distinct integers for distinct 
 * objects. (This is typically implemented by converting the internal 
 * address of the object into an integer, but this implementation 
 * technique is not required by the 
 * Java™ programming language.) 
 * 
 * @return  a hash code value for this object. 
 * @see     java.lang.Object#equals(java.lang.Object) 
 * @see     java.lang.System#identityHashCode 
 */  
public native int hashCode();  
           

大緻意思是:

  1. 在 Java 應用程式執行期間,在對同一對象多次調用 hashCode 方法時,必須一緻地傳回相同的整數,前提是将對象進行 equals 比較時所用的資訊沒有被修改。從某一應用程式的一次執行到同一應用程式的另一次執行,該整數無需保持一緻。
  2. 如果根據 equals(Object) 方法,兩個對象是相等的,那麼對這兩個對象中的每個對象調用 hashCode 方法都必須生成相同的整數結果。
  3. 如果根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼對這兩個對象中的任一對象上調用 hashCode 方法不 要求一定生成不同的整數結果。但是,程式員應該意識到,為不相等的對象生成不同整數結果可以提高哈希表的性能。

hashCode()和equals()保持一緻,如果equals方法傳回true,那麼兩個對象的hasCode()傳回值必須一樣。如果equals方法傳回false,hashcode可以不一樣,但是這樣不利于哈希表的性能,一般我們也不要這樣做。重寫equals()方法就必須重寫hashCode()方法的原因也就顯而易見了。

假設兩個對象,重寫了其equals方法,其相等條件是屬性相等,就傳回true。如果不重寫hashcode方法,其傳回的依然是兩個對象的記憶體位址值,必然不相等。這就出現了equals方法相等,但是hashcode不相等的情況。這不符合hashcode的規則。

重寫的作用

如果重寫(用于需求,比如建立一個Person類,比較相等我隻比較其屬性身份證相等就可不管其他屬性,這時候重寫)equals,就得重寫hashCode,和其對象相等保持一緻。如果不重寫,那麼調用的Object中的方法一定保持一緻。

1. 重寫equals()方法就必須重寫hashCode()方法主要是針對HashSet和Map集合類型。集合架構隻能存入對象(對象的引用(基本類型資料:自動裝箱))。

在向HashSet集合中存入一個元素時,HashSet會調用該對象(存入對象)的hashCode()方法來得到該對象的hashCode()值,然後根據該hashCode值決定該對象在HashSet中存儲的位置。簡單的說:HashSet集合判斷兩個元素相等的标準是:兩個對象通過equals()方法比較相等,并且兩個對象的HashCode()方法傳回值也相等。如果兩個元素通過equals()方法比較傳回true,但是它們的hashCode()方法傳回值不同,HashSet會把它們存儲在不同的位置,依然可以添加成功。

2. 集合類都重寫了toString方法。String類重寫了equal和hashCode方法,比較的是值。

用HashSet來驗證兩個需都重寫的必要性

程式提供了三個類A,B,C,它們分别重寫了equals(),hashCode()兩個方法中的一個或全部。

public class A {  
    @Override  
    public boolean equals(Object obj) {  
        return true;  
    }  
}


public class B {  
    @Override  
    public int hashCode() {  
        return ;  
    }  
}


public class C {  
    @Override  
    public int hashCode() {  
        return ;  
    }  

    @Override  
    public boolean equals(Object obj) {  
        return true;  
    }  
}


public class HashSetTest {  
    public static void main(String[] args) {  
        HashSet hashSet = new HashSet();  
        hashSet.add(new A());  
        hashSet.add(new A());  
        hashSet.add(new B());  
        hashSet.add(new B());  
        hashSet.add(new C());  
        hashSet.add(new C());  
        for (Object hs : hashSet) {  
            System.out.println(hs);  
        }  
        //HashSet重寫了toString()方法  
        //System.out.println(hashSet);  
    }  
}      
           

結果:

cn.edu.uestc.collection.B@1  
cn.edu.uestc.collection.B@1  
cn.edu.uestc.collection.C@2  
cn.edu.uestc.collection.A@3f84246a  
cn.edu.uestc.collection.A@18a9fa9c  
Process finished with exit code   
           

從上邊的程式結果可以看到,必須要同時重寫這兩個方法,要不然Set的特性就被破壞了。

重寫hashCode()的原則

(1)同一個對象多次調用hashCode()方法應該傳回相同的值;

(2)當兩個對象通過equals()方法比較傳回true時,這兩個對象的hashCode()應該傳回相等的(int)值;

(3)對象中用作equals()方法比較标準的Filed(成員變量(類屬性)),都應該用來計算hashCode值。

附上String對hashCode()的實作

/** 
 * Returns a hash code for this string. The hash code for a 
 * <code>String</code> object is computed as 
 * <blockquote><pre> 
 * s[]*^(n-) + s[]*^(n-) + ... + s[n-] 
 * </pre></blockquote> 
 * using <code>int</code> arithmetic, where <code>s[i]</code> is the 
 * <i>i</i>th character of the string, <code>n</code> is the length of 
 * the string, and <code>^</code> indicates exponentiation. 
 * (The hash value of the empty string is zero.) 
 * 
 * @return  a hash code value for this object. 
 */  
public int hashCode() {  
    int h = hash;  
    if (h ==  && value.length > ) {  
        char val[] = value;  

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