Java中的equals方法和hashCode方法是Object中的塌忽,所以每個對象都是有繼承這兩個方法的,有時候我們需要實現(xiàn)特定需求桐经,可能要重寫這兩個方法拓哟。
比如:HashMap、HashTable河泳、HashSet沃呢,所以涉及到使用hash值進行優(yōu)化存儲的地方,都會用到hashCode拆挥。
1.首先調(diào)用對象的hashCode()方法獲得該對象的哈希碼表薄霜。
2.然后根據(jù)哈希嗎找到相應(yīng)的存儲區(qū)域。
3.最后取得該存儲區(qū)域內(nèi)的每個元素與該對象進行equals方法比較纸兔。
這樣就不用遍歷集合中的所有元素就可以得到結(jié)論惰瓜,可見HashSet集合具有很好的對象檢索性能。
hash與equals的關(guān)系:
hashCode就好比字典里每個字的索引汉矿,equals()好比比較的是字典里同一個字下的不同詞語崎坊。
- 不管執(zhí)行多少次獲取hash值的操作,只要對象不變洲拇,那么hash值是固定的
- 若兩個對象equals方法返回為true奈揍,則其hash值也應(yīng)該是一樣的。
- 若兩個對象equals方法返回為false赋续,則其hash值可能相同男翰。
下面來看一下一個具體的例子:
RectObject對象:
package com.weijia.demo;
public class RectObject {
public int x;
public int y;
public RectObject(int x,int y){
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 RectObject other = (RectObject)obj;
if(x != other.x){
return false;
}
if(y != other.y){
return false;
}
return true;
}
}
我們重寫了父類Object中的hashCode和equals方法,看到hashCode和equals方法中纽乱,如果兩個RectObject對象的x,y值相等的話他們的hashCode值是相等的蛾绎,同時equals返回的是true;
下面是測試代碼:
package com.weijia.demo;
import java.util.HashSet;
public class Demo {
public static void main(String[] args){
HashSet<RectObject> set = new HashSet<RectObject>();
RectObject r1 = new RectObject(3,3);
RectObject r2 = new RectObject(5,5);
RectObject r3 = new RectObject(3,3);
set.add(r1);
set.add(r2);
set.add(r3);
set.add(r1);
System.out.println("size:"+set.size());
}
}
我們向HashSet中存入到了四個對象,打印set集合的大小迫淹。
運行結(jié)果:size:2
因為重寫了RectObject類的hashCode方法秘通,只要RectObject對象的x,y屬性值相等那么他的hashCode值也是相等的,先比較hashCode的值敛熬,r1和r2對象的x,y屬性值不等肺稀,所以他們的hashCode不相同的,所以r2對象可以放進去应民,但是r3對象的x,y屬性值和r1對象的屬性值相同的话原,所以hashCode是相等的夕吻,這時候在比較r1和r3的equals方法,因為他么兩的x,y值是相等的繁仁,所以r1,r3對象是相等的涉馅,所以r3不能放進去了,同樣最后再添加一個r1也是沒有沒有添加進去的黄虱,所以set集合中只有一個r1和r2這兩個對象下面我們把RectObject對象中的hashCode方法注釋稚矿,即不重寫hashCode方法,再運行一下代碼:
運行結(jié)果:size:3
這個結(jié)果也是很簡單的捻浦,首先判斷r1對象和r2對象的hashCode晤揣,因為Object中的hashCode方法返回的是對象本地內(nèi)存地址的換算結(jié)果,不同的實例對象的hashCode是不相同的朱灿,同樣因為r3和r1的hashCode也是不相等的昧识,但是r1==r1的,所以最后set集合中只有r1,r2,r3這三個對象盗扒,所以大小是3下面我們把RectObject對象中的equals方法中的內(nèi)容注釋跪楞,直接返回false,放開hashCode方法的注釋侣灶,運行一下代碼:
運行結(jié)果:size:3
這個結(jié)果就有點意外了甸祭,我們來分析一下:
首先r1和r2的對象比較hashCode,不相等炫隶,所以r2放進set中淋叶,再來看一下r3,比較r1和r3的hashCode方法,是相等的伪阶,然后比較他們兩的equals方法煞檩,因為equals方法始終返回false,所以r1和r3也是不相等的,r3和r2就不用說了栅贴,他們兩的hashCode是不相等的斟湃,所以r3放進set中,再看r4,比較r1和r4發(fā)現(xiàn)hashCode是相等的檐薯,在比較equals方法凝赛,因為equals返回false,所以r1和r4不相等,同一r2和r4也是不相等的坛缕,r3和r4也是不相等的墓猎,所以r4可以放到set集合中,那么結(jié)果應(yīng)該是size:4,但為什麼是3呢赚楚?
其實HashSet首先是判斷hashCode是否相等毙沾,不相等的話,直接跳過宠页,相等的話左胞,然后再來比較這兩個對象是否相等或者這兩個對象的equals方法寇仓,因為是進行的或操作,所以只要有一個成立即可烤宙,那這里我們就可以解釋了遍烦,其實上面的那個集合的大小是3,因為最后的一個r1沒有放進去,以為r1==r1返回true的躺枕,所以沒有放進去了服猪。所以集合的大小是3。
最后我們在來看一下hashCode造成的內(nèi)存泄露的問題:看一下代碼:
package com.weijia.demo;
import java.util.HashSet;
public class Demo {
public static void main(String[] args){
HashSet<RectObject> set = new HashSet<RectObject>();
RectObject r1 = new RectObject(3,3);
RectObject r2 = new RectObject(5,5);
RectObject r3 = new RectObject(3,3);
set.add(r1);
set.add(r2);
set.add(r3);
r3.y = 7;
System.out.println("刪除前的大小size:"+set.size());
set.remove(r3);
System.out.println("刪除后的大小size:"+set.size());
}
}
運行結(jié)果:
刪除前的大小size:3
刪除后的大小size:3
調(diào)用了remove刪除r3對象拐云,以為刪除了r3,但事實上并沒有刪除蔓姚,這樣多次這樣操作之后,內(nèi)存就爆了慨丐。因為在調(diào)用remove方法的時候,會先使用對象的hashCode值去找到這個對象泄私,然后進行刪除房揭,但我們在修改了r3對象的y屬性的值,又因為RectObject對象的hashCode方法中有y值參與運算,所以r3對象的hashCode就發(fā)生改變了晌端,所以remove方法中并沒有找到r3了捅暴,所以刪除失敗。即r3的hashCode變了咧纠,但是他存儲的位置沒有更新蓬痒,仍然在原來的位置上,所以當(dāng)我們用他的新的hashCode去找肯定是找不到了漆羔。
要點:
Object中的hashCode方法返回的是對象本地內(nèi)存地址的換算結(jié)果梧奢。
HashSet判斷要放入的元素是否相等:
1.hashcode是否相等,不相等放入演痒。
2.如果hashcode相等亲轨,判斷equals方法和==方法,如果都不相等放入鸟顺。equals和==方法只要有一個相等惦蚊,HashSet認為是相同的元素。