1、首先我們看看對(duì)象默認(rèn)的(Object)的equals方法和hashcode方法
public boolean equals(Object obj) {
return(this== obj);
}
public native int hashCode();
對(duì)象在不重寫的情況下使用的是Object的equals方法和hashcode方法,從Object類的源碼我們知道,默認(rèn)的equals 判斷的是兩個(gè)對(duì)象的引用指向的是不是同一個(gè)對(duì)象;而hashcode也是根據(jù)對(duì)象地址生成一個(gè)整數(shù)數(shù)值;

2、重寫equals
案例場(chǎng)景:
定義一個(gè)User對(duì)象有多個(gè)屬性值姓名、年齡、身份證;
我們寫代碼的時(shí)候會(huì)發(fā)現(xiàn),兩個(gè)new出來(lái)的User()對(duì)象 無(wú)論他們的的各項(xiàng)值是否一樣兩個(gè)對(duì)象equals永遠(yuǎn)都是false,兩個(gè)對(duì)象值完全一樣放到HashSet里面它會(huì)把這兩個(gè)值完全一樣的對(duì)象當(dāng)成兩個(gè)不同的對(duì)象了,這樣的話好像HashSet的特性就丟失了;
其實(shí)原因就是我們沒(méi)有重寫User的equals方法,它會(huì)調(diào)用Object的equals方法,就如上圖一樣,Object的equals方法是比較對(duì)象的引用對(duì)象是否是同一個(gè),兩個(gè)new出來(lái)的對(duì)象當(dāng)然不一樣。
好了現(xiàn)在需求來(lái)了,我們需要兩個(gè)對(duì)象的各項(xiàng)屬性值一樣的就認(rèn)為這兩個(gè)對(duì)象是相等的;那么此時(shí)我們就需要重寫equals方法了;
代碼如下
public classUser {
privateStringname;//姓名
privateStringIdCard;//身份證
private intage;//年齡
/**
* 重寫equals
*@paramobj
*@return
*/
@Override
@Override
public boolean equals(Object obj) {
if(obj ?instanceof ?User) {
User user = (User) obj;
if(user.getIdCard().equals(this.IdCard) && user.getName().equals(this.name) && user.getAge() ==this.age) {
return true;
}else{
return false;
}
}else{
return false;
}
}
//......省略N行代碼
}
那么現(xiàn)在關(guān)鍵的地方來(lái)了:現(xiàn)在我們重寫了User對(duì)象的equals方法,但并沒(méi)有重寫hashcode方法。
(1)首先測(cè)試下equals的正確性
User user1=newUser();
user1.setName("路西");
user1.setAge(18);
user1.setIdCard("430");
User user2=newUser();
user2.setName("路西");
user2.setAge(18);
user2.setIdCard("430");
System.out.println("user1.equals(user2)="+user1.equals(user2));
user1.equals(user2)測(cè)試結(jié)果為true;
(2)在兩個(gè)對(duì)象equals的情況下進(jìn)行把他們分別放入Map和Set中
在上面的代碼基礎(chǔ)上追加如下代碼:
Set set =newHashSet();
set.add(user1);
set.add(user2);
Map map=newHashMap();
map.put(user1,"user1");
map.put(user2,"user2");
System.out.println("set 長(zhǎng)度"+set.size());
System.out.println("map 長(zhǎng)度"+map.keySet().size());;
測(cè)試打印結(jié)果為:

好了現(xiàn)在問(wèn)題來(lái)了,明明user1和user2兩個(gè)對(duì)象是equals的那么為什么把他們放到set中會(huì)有兩個(gè)對(duì)象(Set特性是不允許重復(fù)數(shù)據(jù)的),還有Map也把兩個(gè)同樣的對(duì)象當(dāng)成了不同的Key(Map的Key是不允許重復(fù)的,相同Key會(huì)覆蓋);
(3)這里我先拋出結(jié)果,至于原理后面再進(jìn)行描述
原因是user1和user2的hashcode 不一樣導(dǎo)致的;

因?yàn)槲覀儧](méi)有重寫父類(Object)的hashcode方法,Object的hashcode方法會(huì)根據(jù)兩個(gè)對(duì)象的地址生成對(duì)相應(yīng)的hashcode;
user1和user2是分別new出來(lái)的,那么他們的地址肯定是不一樣的,自然hashcode值也會(huì)不一樣。
Set區(qū)別對(duì)象是不是唯一的標(biāo)準(zhǔn)是,兩個(gè)對(duì)象hashcode是不是一樣,再判定兩個(gè)對(duì)象是否equals;
Map 是先根據(jù)Key值的hashcode分配和獲取對(duì)象保存數(shù)組下標(biāo)的,然后再根據(jù)equals區(qū)分唯一值(詳見(jiàn)下面的map分析)
3、重寫hashcode方法;
public classUser? {
privateStringname;//姓名
privateStringIdCard;//身份證
private intage;//年齡
* 重寫equals
*@paramobj
*@return
*/
@Override
public boolean equals(Object obj) {
if(obj ?instanceof ?User) {
User user = (User) obj;
if(user.getIdCard().equals(this.IdCard) && user.getName().equals(this.name) && user.getAge() ==this.age) {
return true;
}else{
return false;
}
}else{
return false;
}
}
@Override
public int hashCode() {
intresult =name.hashCode();
result =31* result +IdCard.hashCode();
result =31* result +age;
returnresult;
}
//......省略N行代碼
}
我們按之前的流程重新測(cè)試一遍結(jié)果:
User user1=newUser();
user1.setName("路西");
user1.setAge(18);
user1.setIdCard("430");
User user2=newUser();
user2.setName("路西");
user2.setAge(18);
user2.setIdCard("430");
System.out.println("user1.equals(user2)="+user1.equals(user2));
Set set =newHashSet();
set.add(user1);
set.add(user2);
Map map=newHashMap();
map.put(user1,"user1");
map.put(user2,"user2");
System.out.println("set 長(zhǎng)度"+set.size());
System.out.println("map 長(zhǎng)度"+map.keySet().size());;
System.out.println("user1的hashcode"+user1.hashCode());
System.out.println("user2的hashcode"+user2.hashCode());
打印結(jié)果:

4、補(bǔ)充HashMap知識(shí)
hashMap組成結(jié)構(gòu):hashMap是由數(shù)組和鏈表組成;
hashMap的存儲(chǔ):一個(gè)對(duì)象存儲(chǔ)到hashMap中的位置是由其key 的hashcode值決定的;查hashMap查找key: 找key的時(shí)候hashMap會(huì)先根據(jù)key值的hashcode經(jīng)過(guò)取余算法定位其所在數(shù)組的位置,再根據(jù)key的equals方法匹配相同key值獲取對(duì)應(yīng)相應(yīng)的對(duì)象;
案例:
(1)hashmap存儲(chǔ)
存值規(guī)則:把Key的hashCode 與HashMap的容量 取余得出該Key存儲(chǔ)在數(shù)組所在位置的下標(biāo)(源碼定位Key存儲(chǔ)在數(shù)組的哪個(gè)位置是以hashCode & (HashMap容量-1)算法得出)這里為方便理解使用此方式;
//為了演示方便定義一個(gè)容量大小為3的hashMap(其默認(rèn)為16)
HashMap map=newHashMap(3);
map.put("a",1);? ? 得到key 為“a” 的hashcode 值為97然后根據(jù) 該值和hashMap 容量取余97%3得到存儲(chǔ)位到數(shù)組下標(biāo)為1;
map.put("b",2);? ? 得到key 為“b” 的hashcode 值為98,98%3到存儲(chǔ)位到數(shù)組下標(biāo)為2;
map.put("c",3);? ? 得到key 為“c” 的hashcode 值為99,99%3到存儲(chǔ)位到數(shù)組下標(biāo)為0;
map.put("d",4);? ? 得到key 為“d” 的hashcode 值為100,100%3到存儲(chǔ)位到數(shù)組下標(biāo)為1;
map.put("e",5);? ? 得到key 為“e” 的hashcode 值為101,101%3到存儲(chǔ)位到數(shù)組下標(biāo)為2;
map.put("f",6);? ? 得到key 為“f” 的hashcode 值為102,102%3到存儲(chǔ)位到數(shù)組下標(biāo)為0;

(2)hashmap的查找key
得到key在數(shù)組中的位置:根據(jù)上圖,當(dāng)我們獲取key 為“a”的對(duì)象時(shí),那么我們首先獲得 key的hashcode97%3得到存儲(chǔ)位到數(shù)組下標(biāo)為1;
匹配得到對(duì)應(yīng)key值對(duì)象:得到數(shù)組下表為1的數(shù)據(jù)“a”和“c”對(duì)象, 然后再根據(jù) key.equals()來(lái)匹配獲取對(duì)應(yīng)key的數(shù)據(jù)對(duì)象;
hashcode 對(duì)于HashMapde:如果沒(méi)有hashcode 就意味著HashMap存儲(chǔ)的時(shí)候是沒(méi)有規(guī)律可尋的,那么每當(dāng)我們map.get()方法的時(shí)候,就要把map里面的對(duì)象一一拿出來(lái)進(jìn)行equals匹配,這樣效率是不是會(huì)超級(jí)慢;
5、hashcode方法文檔說(shuō)明
在equals方法沒(méi)被修改的前提下,多次調(diào)用同一對(duì)象的hashcode方法返回的值必須是相同的整數(shù);
如果兩個(gè)對(duì)象互相equals,那么這兩個(gè)對(duì)象的hashcode值必須相等;
為不同對(duì)象生成不同的hashcode可以提升哈希表的性能;