(1)前言,想要明白hashCode的作用,你必須要先知道Java中的集合。
Java中的集合(Collection)有兩類,一類是List,再有一類是Set。
前者集合內的元素是有序的,元素可以重復;后者元素無序,但元素不可重復。
那么我們怎么判斷兩個元素是否重復呢? 這就是Object.equals方法了。
通常想查找一個集合中是否包含某個對象,就是逐一取出每個元素與要查找的元素進行比較,當發現某個元素與要查找的對象進行equals方法比較的結果相等時,則停止繼續查找並返回肯定的信息,否則返回否定的信息,如果一個集合中有很多元素譬如成千上萬的元素,並且沒有包含要查找的對象時,則意味着你的程序需要從該集合中取出成千上萬個元素進行逐一比較才能得到結論,於是,有人就發明了一種哈希算法來提高從集合中查找元素的效率,這種方式將集合分成若干個存儲區域,每個對象可以計算出一個哈希碼,可以將哈希碼分組,每組分別對應某個存儲區域,根據一個對象的哈希碼就可以確定該對象應該存儲的那個區域。
hashCode方法可以這樣理解:它返回的就是根據對象的內存地址換算出的一個值。這樣一來,當集合要添加新的元素時,先調用這個元素的hashCode方法,就一下子能定位到它應該放置的物理位置上。如果這個位置上沒有元素,它就可以直接存儲在這個位置上,不用再進行任何比較了;如果這個位置上已經有元素了,就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址。這樣一來實際調用equals方法的次數就大大降低了,幾乎只需要一兩次。
(2)首先,equals()和hashcode()這兩個方法都是從object類中繼承過來的。
equals()方法在object類中定義如下:
public boolean equals(Object obj) { return (this == obj); }
很明顯是對兩個對象的地址值進行的比較(即比較引用是否相同)。但是我們必需清楚,當String、Math、還有Integer、Double。。。。等這些封裝類在使用equals()方法時,已經覆蓋了object類的equals()方法。比 如在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; }
很明顯,這是進行的內容比較,而已經不再是地址的比較。依次類推Double、Integer、Math。。。。等等這些類都是重寫了equals()方法的,從而進行的是內容的比較。
我們還應該注意,Java語言對equals()的要求如下,這些要求是必須遵循的:
1) 對稱性:如果x.equals(y)返回是"true",那么y.equals(x)也應該返回是"true"。
2) 反射性:x.equals(x)必須返回是"true"。
3) 類推性:如果x.equals(y)返回是"true",而且y.equals(z)返回是"true",那么z.equals(x)也應該返回是"true"。
4) 還有一致性:如果x.equals(y)返回是"true",只要x和y內容一直不變,不管你重復x.equals(y)多少次,返回都是"true"。
5) 任何情況下,x.equals(null),永遠返回是"false";x.equals(和x不同類型的對象)永遠返回是"false"。
以上這五點是重寫equals()方法時,必須遵守的准則,如果違反會出現意想不到的結果,請大家一定要遵守。
(3)其次,hashcode() 方法,在object類中定義如下:
public native int hashCode();
說明它是一個本地方法,它的實現是根據本地機器相關的。當然我們可以在自己寫的類中覆蓋hashcode()方法,比如String、Integer、Double等這些類都是覆蓋了hashcode()方法的。例如在String類中定義的hashcode()方法如下:
public int hashCode() { int h = hash; if (h == 0) { int off = offset; char val[] = value; int len = count; for (int i = 0; i < len; i++) { h = 31*h + val[off++]; } hash = h; } return h; }
解釋一下這個程序(String的API中寫到):
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
使用 int 算法,這里 s[i] 是字符串的第 i 個字符,n 是字符串的長度,^ 表示求冪。(空字符串的哈希碼為 0。)
(4)談到hashcode()和equals()就不能不說到hashset,hashmap,hashtable中的使用,具體是怎樣呢,請看如下分析:
Hashset是繼承Set接口,Set接口又實現Collection接口,這是層次關系。那么hashset是根據什么原理來存取對象的呢?
在hashset中不允許出現重復對象,元素的位置也是不確定的。在hashset中又是怎樣判定元素是否重復的呢?判斷兩個對象是否相等的規則是:
1),判斷兩個對象的hashCode是否相等。
如果不相等,認為兩個對象也不相等,完畢,如果相等,轉入2
2),判斷兩個對象用equals運算是否相等。
如果不相等,認為兩個對象也不相等 。
如果相等,認為兩個對象相等(equals()是判斷兩個對象是否相等的關鍵) 。
為什么是兩條准則,難道用第一條不行嗎?不行,因為前面已經說了,hashcode()相等時,equals()方法也可能不等,所以必須用第2條准則進行限制,才能保證加入的為非重復元素。
比如下面的代碼:
public static void main(String[] args) { String s1 = new String("zhangsan"); String s2 = new String("zhangsan"); System.out.println(s1 == s2);// false System.out.println(s1.equals(s2));// true System.out.println(s1.hashCode());// s1.hashcode()等於s2.hashcode() System.out.println(s2.hashCode()); Set hashset = new HashSet(); hashset.add(s1); hashset.add(s2); System.out.println(hashset.size());//1 }
再看如下一些示例:
幾個很簡單的示例,說明一些很簡單的道理
例一:
package com.itsoft; import java.util.ArrayList; import java.util.Collection; public class Point { private int x; private int y; public Point(int x, int y) { super(); this.x = x; this.y = y; } public static void main(String[] args) { Point p1 = new Point(3, 3); Point p2 = new Point(5, 5); Point p3 = new Point(3, 3); Collection<Point> collection = new ArrayList<Point>(); collection.add(p1); collection.add(p2); collection.add(p3); collection.add(p1); System.out.println(collection.size());//4,結果輸出4,以為List中可以有重復元素,而且是有序的。 } }
例二(在上例的基礎上稍作修改把ArrayList改為HashSet):
package com.itsoft; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; public class Point { private int x; private int y; public Point(int x, int y) { super(); this.x = x; this.y = y; } public static void main(String[] args) { Point p1 = new Point(3, 3); Point p2 = new Point(5, 5); Point p3 = new Point(3, 3); Collection<Point> collection = new HashSet<Point>(); collection.add(p1); collection.add(p2); collection.add(p3); collection.add(p1); System.out.println(collection.size());//3,因為HashSet中不會保存重復的對象,每添加一個元素,先判斷,再添加,如果已經存在,那么就不在添加,無序的! }
}
例三(如果我們需要p1和p3相等呢?就必須重新hashcode()和equal()方法):
package com.itsoft; import java.util.Collection; import java.util.HashSet; public class Point { private int x; private int y; public Point(int x, int y) { super(); this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final Point other = (Point) obj; if (x != other.x) return false; if (y != other.y) return false; return true; } public static void main(String[] args) { Point p1 = new Point(3, 3); Point p2 = new Point(5, 5); Point p3 = new Point(3, 3); Collection<Point> collection = new HashSet<Point>(); collection.add(p1); collection.add(p2); collection.add(p3); collection.add(p1); System.out.println(collection.size());//輸出2,此時p1和p3是相等的 } }
例四(如果我們把hashcode()方法去掉看下):
package com.itsoft; import java.util.Collection; import java.util.HashSet; public class Point { private int x; private int y; public Point(int x, int y) { super(); this.x = x; this.y = y; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final Point other = (Point) obj; if (x != other.x) return false; if (y != other.y) return false; return true; } public static void main(String[] args) { Point p1 = new Point(3, 3); Point p2 = new Point(5, 5); Point p3 = new Point(3, 3); Collection<Point> collection = new HashSet<Point>(); collection.add(p1); collection.add(p2); collection.add(p3); collection.add(p1); System.out.println(collection.size());//輸出3,此時p1和p3又不相等了 //原因:雖然此時p1和p2的equals相等,但是他們的hashcode不相等,所以它們就存儲在不同區域,在這兩個不同的區域存儲着相同的東西,查找的時候只在一個區域查找,就被放進去了。 } }
注:為了避免第四種情況的發生,通常情況下,一個實例的兩個對象equals相同,那么他們的hashcode也必須相等,反之,則不成立,當然,只有對象存儲在hash算法系列的集合中,hashcode方法才有價值.這樣目的就是確保相同的對象存儲在相同的位置。
小結:
(1)只有類的實例對象要被采用哈希算法進行存儲和檢索時,這個類才需要按要求覆蓋hashCode方法,即使程序可能暫時不會用到當前類的hashCode方法,但是為它提供一個hashCode方法也不會有什么不好,沒准以后什么時候又用到這個方法了,所以,通常要求hashCode方法和equals方法一並被同時覆蓋。
(2)equals()相等的兩個對象,hashcode()一定相等;equals()不相等的兩個對象,卻並不能證明他們的hashcode()不相等。換句話說,equals()方法不相等的兩個對象,hashcode()有可能相等。反過來:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。
提示:
(1)通常來說,一個類的兩個實例對象用equal方法比較的結果相等時,它們的哈希碼也必須相等,但反之則不成立,即equals方法比較結果不相等的對象可以有相同的哈希碼,或者說哈希碼相同的兩個對象的equal方法比較的結果可以不等。
(2)當一個對象被存儲進hashset集合中以后,就不能修改這個對象中的那些參與計算哈希值的字段了,否則,對象修改后的哈希值與最初存儲進hashset集合時的哈希值就不同了,這種情況下,即使在contains方法使用該對象的當前引用作為的參數去hashset集合中檢索對象,也將返回找不到對象的結果,這也會導致無法從hashset集合中單獨刪除當前對象,從而造成內存泄露,所謂的內存泄露也就說有一個對象不再被使用,但它一直占有內存空間,沒有被釋放。