歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java中hashcode,equals和==

Java中hashcode,equals和==

日期:2017/4/19 14:16:56   编辑:Linux編程

hashcode方法返回該對象的哈希碼值。

hashCode()方法可以用來來提高Map裡面的搜索效率的,Map會根據不同的hashCode()來放在不同的位置,Map在搜索一個對象的時候先通過hashCode()找到相應的位置,然後再根據equals()方法判斷這個位置上的對象與當前要插入的對象是不是同一個。若兩個對象equals相等,但不在一個區間,根本沒有機會進行比較,會被認為是不同的對象。

所以,Java對於eqauls方法和hashCode方法是這樣規定的:

1、如果兩個對象相同,那麼它們的hashCode值一定要相同,也告訴我們重寫equals方法,一定要重寫hashCode方法;

2、如果兩個對象的hashCode相同,它們並不一定相同

hashCode()和equals()定義在Object類中,這個類是所有java類的基類,所以所有的java類都繼承這兩個方法。

注意到hashCode方法前面有個native的修飾符,這表示hashCode方法是由非java語言實現的,說明是一個本地方法,它的實現是根據本地機器相關的。具體的方法實現在外部,返回內存對象的地址。

/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hashtables such as those provided by
* <code>java.util.Hashtable</code>.
* <p>
* The general contract of <code>hashCode</code> is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the <tt>hashCode</tt> method
* must consistently return the same integer, provided no information
* used in <tt>equals</tt> 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 <tt>equals(Object)</tt>
* method, then calling the <code>hashCode</code> 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 <tt>hashCode</tt> 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 hashtables.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class <tt>Object</tt> 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<font size="-2"><sup>TM</sup></font> programming language.)
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.util.Hashtable
*/
public native int hashCode();

在java類中可以重寫這兩個方法,下面是String類中這兩個類的實現。

/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}

/**
* 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;
int len = count;
if (h == 0 && len > 0) {
int off = offset;
char val[] = value;

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

String類中equals是基於內容的比較,而不是基於地址的比較。

Java語言對equals()的要求如下,這些要求是必須遵循的:

• 對稱性:如果x.equals(y)返回是“true”,那麼y.equals(x)也應該返回是“true”。

• 反射性: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()、hashCode()都和對象的比較有關,在java中這三者各有什麼用處呢,即java中為什麼需要設計這三種對象的比較方法呢?

1.關於==

==是容易理解的。java設計java就是要比較兩個對象是不是同一個對象。

對於引用變量而言,比較的時候兩個引用變量引用的是不是同一個對象,即比較的是兩個引用中存儲的對象地址是不是一樣的。

對於基本數據類型而言,比較的就是兩個數據是不是相等,沒什麼歧義。

由於對於基本數據類型而言,沒有方法,所以不存在equal()和hashCode()的問題,下面的討論都是針對引用類型而言的。

2.關於equals()

為什麼java會設計equals()方法?

==比較的是兩個對象是否是同一個對象,這並不能滿足很多需求。有時候當兩個對象不==的時候,我們仍然會認為兩者是“相等”的,比如對於String對象,當兩個對象的字符串序列是一直的,我們就認為他們是“相等”的。對於這樣的需求,需要equals()來實現。對於有這種需求的對象的類,重寫其equals()方法便可,具體的“相等”邏輯可以根據需要自己定義。

需要注意的地方

Object中equals()的默認實現是比較兩個對象是不是==,即其和==的效果是相同的。

java提供的某些類已經重寫了equals()方法。自己寫的類,如果需要實現自己的“相等”邏輯,需要重寫equals()方法。

3.關於hashCode()

為什麼會設計hashCode()方法?

hashCode()方法返回的就是一個數值,我們稱之為hashCode吧。從方法的名稱上就可以看出,其目的是生成一個hash碼。hash碼的主要用途就是在對對象進行散列的時候作為key輸入,據此很容易推斷出,我們需要每個對象的hash碼盡可能不同,這樣才能保證散列的存取性能。事實上,Object類提供的默認實現確實保證每個對象的hash碼不同(在對象的內存地址基礎上經過特定算法返回一個hash碼)。

分析到這個地方,看似沒什麼問題,三者的作用很清晰,好像他們之間也沒什麼關系。在java的規范上,hashCode()方法和equals()方法確實可以沒有關系。

但是!!!!!!!!有一個問題。

問題如下:對於集合類HashSet、HashMap等和hash有關的類(以HashSet為例),是通過hash算法來散列對象的。對HashSet而言,存入對象的流程為:根據對象的hash碼,經過hash算法,找到對象應該存放的位置,如果該位置為空,則將對象存入該位置;如果該位置不為空,則使用equals()比較該位置的對象和將要入的對象,如果兩個相等,則不再插入,如果不相等,根據hash沖突解決算法將對象插入其他位置。

而java規定對於HashSet判斷是不是重復對象就是通過equals() 方法來完成,這就需要在兩個對象equals()方法相等的時候,hash碼一定相等(即hashCode()返回的值相等)。假設兩個對象equals()方法相等的時候,hash碼不相等,會出現equals()相等的兩個對象都插入了HashSet中,這時不允許的。從而我們有了一下的結論:

結論:對於equals()相等的兩個對象,其hashCode()返回的值一定相等

通過上面的分析,對於這個結論是沒有異議的。結合前面關於hash碼要盡可能不同的要求,現在變成了對於equals()相等的對象hash碼要一定相等,而對於equals()不同的對象要盡量做到hash碼不同。那麼怎麼才能保證這一點呢?

4.重寫hashCode()

首先,如何保證“對於equals()相等的對象hash碼要一定相等”。

equals()方法中對於對象的比較是通過比較對象中全部或者部分字段來完成的,這些字段集合記為集合A,如果我們計算hash碼的時候,如果只是從集合A中選取部分字段或者全部字段來完成便可,因為輸入相同,不管經過什麼算法,輸出一定相同(在方法中調用隨機函數?這屬於吃飽了撐的!)。如此設計便保證滿足了第一個要求。

其次,對於equals()不同的對象要盡量做到hash碼不同。

對於這一點的保證就是在設計一個好的算法,讓不同的輸入盡可能產生不同的輸出。

下面就詳細介紹一下如何設計這個算法。這個算法是有現成的參考的,算法的具體步驟就是:

[1]把某個非零常數值(一般取素數),例如17,保存在int變量result中;

[2]對於對象中每一個關鍵域f(指equals方法中考慮的每一個域):

[2.1]boolean型,計算(f ? 0 : 1);

[2.2]byte,char,short型,計算(int)f;

[2.3]long型,計算(int) (f ^ (f>>>32));

[2.4]float型,計算Float.floatToIntBits(afloat);

[2.5]double型,計算Double.doubleToLongBits(adouble)得到一個long,再執行[2.3];

[2.6]對象引用,遞歸調用它的hashCode方法;

[2.7]數組域,對其中每個元素調用它的hashCode方法。

[3]將上面計算得到的散列碼保存到int變量c,然後執行 result=37*result+c;

[4]返回result。

其實其思路就是:先去一個基數,然後對於equals()中考慮的每一個域,先轉換成整數,再執行result=37*result+c;

Copyright © Linux教程網 All Rights Reserved