詳解重寫equals()方法就必須重寫hashCode()方法的原因


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

先來看Object關於hashCode()和equals()的源碼:


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();
    簡單的翻譯一下就是,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會把它們存儲在不同的位置,依然可以添加成功。同樣:在Map集合中,例如其子類Hashtable(jdk1.0錯誤的命名規范),HashMap,存儲的數據是<key,value>對,key,value都是對象,被封裝在Map.Entry,即:每個集合元素都是Map.Entry對象。在Map集合中,判斷key相等標准也是:兩個key通過equals()方法比較返回true,兩個key的hashCode的值也必須相等。判斷valude是否相等equal()相等即可。
      稍微提一句:(1)兩個對象,用==比較比較的是地址,需采用equals方法(可根據需求重寫)比較。
                              (2)重寫equals()方法就重寫hashCode()方法。
                               (3)一般相等的對象都規定有相同的hashCode。
                                hash:散列,Map關聯數組,字典
     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 1;
}
}

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

@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 0
    從上邊的程序結果可以看到,必須要同時重寫這兩個方法,要不然Set的特性就被破壞了。

     重寫hashCode()的原則
    (1)同一個對象多次調用hashCode()方法應該返回相同的值;
    (2)當兩個對象通過equals()方法比較返回true時,這兩個對象的hashCode()應該返回相等的(int)值;
    (3)對象中用作equals()方法比較標准的Filed(成員變量(類屬性)),都應該用來計算hashCode值。
      計算hashCode值的方法:      
//f是Filed屬性
boolean hashCode=(f?0:1)
(byte,short,char,int) hashCode=(int)f
long hashCode=(int)(f^(f>>>32))
float hashCode=Float.floatToIntBits(f)
double hashCode=(int)(1^(1>>>32))
普通引用類型 hashCode=f.hashCode()
       將計算出的每個Filed的hashCode值相加返回,為了避免直接相加產生的偶然相等(單個不相等,加起來就相等了),為每個Filed乘以一個質數后再相加,例如有:
return f1.hashCode()*17+(int)f2.13
     
    查看String源碼,看hashCode()d的實現方法:


/**
* Returns a hash code for this string. The hash code for a
* <code>String</code> object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </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 == 0 && value.length > 0) {
char val[] = value;

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


---------------------
作者:冷面寒槍biu
來源:CSDN
原文:https://blog.csdn.net/u013679744/article/details/57074669
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM