第二篇文章主要講解 hashCode 方法篮撑,分為以下部分:
- 關(guān)于 hashCode
- hashCode 源碼
- 重寫 hashCode 原因
- 如何重寫 hashCode
關(guān)于 hashCode
Java中的集合(Collection)有兩類舌狗,一類是 List,再有一類是 Set辜羊。前者集合內(nèi)的元素是有序的,元素可以重復(fù);后者元素?zé)o序拍棕,但元素不可重復(fù)解总。
兩個元素是否重復(fù)應(yīng)該依據(jù)什么來判斷呢贮匕?
答案是 equals 方法。如果每增加一個元素就檢查一次花枫,在集合類的元素較少時刻盐,執(zhí)行效率還算不錯,但是那么當(dāng)元素很多時劳翰,后添加到集合中的元素比較的次數(shù)就非常多了敦锌。也就是說,如果集合中現(xiàn)在已經(jīng)有 1000 個元素佳簸,那么第 1001 個元素加入集合時乙墙,它就要調(diào)用 1000 次 equals 方法。這顯然會大大降低效率。
一般情況下伶丐,計算機存儲數(shù)據(jù)時悼做,將數(shù)據(jù)連續(xù)地存儲在內(nèi)存單元中,于是哗魂,Java集合類在存儲數(shù)據(jù)時肛走,引進了hashCode方法,它采用了一種稱為哈希算法录别,即將數(shù)據(jù)通過特定算法指定到某個內(nèi)存單元朽色。
當(dāng)集合要添加新的元素時,先調(diào)用這個元素的hashCode方法组题,就一下子能定位到它應(yīng)該放置的物理位置上葫男。如果這個位置上沒有元素,它就可以直接存儲在這個位置上崔列,不用再進行任何比較了梢褐;如果這個位置上已經(jīng)有元素了,就調(diào)用它的 equals 方法與新元素進行比較赵讯,相同的話就不存了盈咳,不相同就散列其它的地址。所以這里存在一個沖突解決的問題边翼。這樣一來實際調(diào)用 equals 方法的次數(shù)就大大降低了鱼响,幾乎只需要一兩次。
所以组底,Java 對于 eqauls 方法和 hashCode 方法是這樣規(guī)定的:
1.如果兩個對象相同丈积,那么它們的 hashCode 值一定要相同;
2.如果兩個對象的 hashCode 相同债鸡,它們并不一定相同(這里說的對象相同指的是用 eqauls 方法比較)江滨。
- equals() 相等的兩個對象,hashCode() 一定相等娘锁;equals() 不相等的兩個對象牙寞,卻并不能證明他們的 hashCode() 不相等。
注: 規(guī)定 2 中指的是兩個對象在哈希存儲時發(fā)生了沖突莫秆。
hashCode 源碼
public native int hashCode();
hashCode() 是一個本地方法间雀,返回這個對象的哈希值,默認是返回該對象的內(nèi)存地址镊屎。重寫此方法可以提高哈希結(jié)構(gòu)的集合的性能惹挟。
重寫 hashCode 原因
對于 Java 集合類,我們經(jīng)常使用 Set 集合來保存相關(guān)對象缝驳,而 Set 集合是不允許重復(fù)的连锯。在向 HashSet 集合中添加元素時归苍,其實只要重寫 equals() 這一條也可以。但當(dāng) HashSet 中元素比較多時运怖,或者是重寫的 equals() 方法比較復(fù)雜時拼弃,我們只用 equals() 方法進行比較判斷,效率也會非常低摇展,所以引入了 hashCode() 這個方法吻氧,只是為了提高效率,且這是非常有必要的咏连。
簡單來說盯孙,hashCode存在的意義主要是提供查找的快捷性,比如說在Hashtable祟滴、HashMap中等振惰。hashCode是用來在散列存儲結(jié)構(gòu)中確定對象存儲的位置的;
如何重寫 hashCode
重寫 hashCode 所要遵循的原則如下:
- 在程序執(zhí)行期間,只要 equals 方法的比較操作用到的信息沒有被修改垄懂,那么對這同一個對象調(diào)用多次骑晶,hashCode 方法必須始終如一地返回同一個整數(shù)
- 如果兩個對象通過 equals 方法比較得到的結(jié)果是相等的,那么對這兩個對象進行hashCode得到的值應(yīng)該相同
- 兩個不同的對象草慧,hashCode 的結(jié)果可能是相同的透罢,這就是哈希表中的沖突。為了保證哈希表的效率冠蒋,哈希算法應(yīng)盡可能的避免沖突
下面介紹如何來重寫hashCode()方法。通常重寫hashCode()方法按以下設(shè)計原則實現(xiàn)乾胶。
- 把某個非零素數(shù)抖剿,例如17,保存在int型變量result中识窿。
- 對于對象中每一個關(guān)鍵域f(指equals方法中考慮的每一個域)參照以下原則處理斩郎。
- boolean型,計算(f?0:1)喻频。
- byte缩宜、char和short型,計算(int)f甥温。
- long型锻煌,計算(int)(f^(f>>32))。
- float型姻蚓,計算Float.floatToIntBits(f)。
- double型,計算Double.doubleToLongBits(f)得到一個long瘪板,再執(zhí)行l(wèi)ong型的處理淑仆。
- 對象引用释涛,遞歸調(diào)用它的hashCode()方法。
- 數(shù)組域倦沧,對其中的每個元素調(diào)用它的hashCode()方法唇撬。
- 將上面計算得到的散列碼保存到int型變量c,然后執(zhí)行result = 37 * result + c展融,并返回窖认。
根據(jù)上面的理解,我們進行相關(guān)測試愈污。
Person類的 hashCode 重寫如下:
@Override
public int hashCode() {
int result = 17;
result = 37 * result + name.hashCode();
return result;
}
Employee類的 hashCode 重寫如下:
@Override
public int hashCode() {
int result = 17;
result = 37 * result + super.hashCode();
result = 37 * result + id;
return result;
}
測試類 HashCodeTest 源碼如下:
public class HashCodeTest {
public static void main(String[] args) {
Employee e1 = new Employee("Mary", 18);
Employee e2 = new Employee("Mary", 19);
Person p1 = new Person("Mary");
Person p2 = new Person("Mary");
System.out.println("p1.equals(e1)'s rsult:" + p1.equals(e1));
System.out.println("p1.equals(e2)'s rsult:" + p1.equals(e2));
System.out.println("e1.equals(e2)'s rsult:" + e1.equals(e2));
System.out.println("p1.equals(p2)'s rsult:" + p1.equals(p2));
System.out.println("p1.hashCode is :" + p1.hashCode());
System.out.println("p2.hashCode is :" + p2.hashCode());
System.out.println("e1.hashCode is :" + e1.hashCode());
System.out.println("e2.hashCode is :" + e2.hashCode());
}
}
測試結(jié)果如下:
p1.equals(e1)'s rsult:false
p1.equals(e2)'s rsult:false
e1.equals(e2)'s rsult:false
p1.equals(p2)'s rsult:true
p1.hashCode is :2391408
p2.hashCode is :2391408
e1.hashCode is :88505387
e2.hashCode is :88505388
即 equals() 相等的兩個對象耀态,hashcode() 一定相等。創(chuàng)建一個類時暂雹,我們需要重寫該類的 equals 和 hashCode 方法首装,若不對其進行重寫,則會默認為 Object 類的 equals 和 hashCode 方法杭跪。