好吧好吧,我承認這有點標題黨的嫌疑,我這不是隔太久沒更新,有點興奮么。
板磚拍夠了,臭雞蛋扔夠了,別來打醬油便行了。我這就進入正題。其實正確的標題應該叫Effective Java讀書心得之雞翅的故事。
關于雞翅的故事,相傳最近最近以前……
1 import static org.junit.Assert.*;
2
3 import java.util.HashMap;
4
5 import org.junit.Test;
6
7
8 public class TestObjectHashCode {
9
10 @Test
11 public void testIt(){
12 //最近,麥當當推出了麥香翅,味道不是一般的好,那是相當?shù)暮谩?br />
13 //于是受到消費者的青睞,大受歡迎。
14 雞翅 麥香翅=new 雞翅("麥香翅");
15 味道 味道好極了=new 味道("味道好極了");
16 HashMap 市場=new HashMap();
17 市場.put(麥香翅, 味道好極了);
18
19 //這一切都被一個山寨小食店看在眼里,他們決定打著麥香翅的名號,推出實際上味道一般的山寨麥香翅
20 雞翅 山寨麥香翅=new 雞翅("麥香翅");
21
22 //山寨小食店的師傅還是很有智慧的,他們通過某某方式,通過ISO叉叉叉叉的認證。
23 //他們很天真地認為他們的山寨翅可以媲美麥香翅。
24 assertTrue(山寨麥香翅.equals(麥香翅));
25
26 //但是結果大家都知道了,山寨翅并沒有獲得市場的認可,魚目混珠終究被市場識別出來。
27 assertFalse(味道好極了.equals(市場.get(山寨麥香翅)));
28
29 //山寨小食店苦思瞑想,終于發(fā)現(xiàn)了問題,原來他們指打相同的名號是不行的。
30 //他們的并沒有山寨出麥香翅代號為HashCode的秘制醬料
31 assertFalse(麥香翅.hashCode()==山寨麥香翅.hashCode());
32 }
33
34 public static final class 味道{
35 private String description;
36
37 public 味道(String des){
38 description=des;
39 }
40
41 public String getDescription() {
42 return description;
43 }
44
45 public void setDescription(String aDes) {
46 this.description = aDes;
47 }
48
49 }
50
51 public static final class 雞翅 {
52
53 private String Name;
54
55 public 雞翅(String name){
56 this.Name=name;
57 }
58
59 public String getName() {
60 return Name;
61 }
62
63 public void setName(String aName) {
64 this.Name = aName;
65 }
66
67 @Override
68 public boolean equals(Object obj){
69 if(!(obj instanceof 雞翅)){
70 return false;
71 }
72 return ((雞翅)obj).getName().equals(this.Name);
73 }
74 }
75
76 }
77
看完了不知道我想說啥?看來沒認真看《Effective Java》嘛,這本書可是每個java程序員進階必修之書,沒看過真不能自稱大蝦級別。好了,這里說到了正題的正題,其實我想說的是:
《Effective Java》第八條:改寫equals時總是要改寫hashCode。
*為什么要改寫hashCode
答:(書上原話)“如果不這樣做的話,就會違反Object.hashCode的通用約定,從而導致該類無法與所有基于散列值(hash)的集合類在一起正常工作,這樣的集合類包括HashMap,HashSet和Hashtable.
*那么為什么會導致該類無法與所有基于散列值(hash)的集合類在一起正常工作呢?
答:且看一個HashMap的源代碼解釋。
HashMap是通過一個叫table[]的數(shù)組來存取,table的每個元素是一個鏈表結構,鏈表的每個元素稱為 Entry<key,value>,是真正存放key和value的對象。在put的過程中,HashMap會通過特定的哈希算法將key對象的hashCode對應到table的某個索引下,然后再用key對比鏈表中每個Entry.key,如果key相同則更新value。否則就加入新的 Entry到鏈表中。
HashMap的數(shù)據(jù)結構圖:
再看一段HashMap的源代碼:
1 public V put(K key, V value) {
2 K k = maskNull(key);// 如果key為null則使用缺省的Object
3 int hash = hash(k);//將k的hashCode經(jīng)過一定的計算得到新的HashCode
4 int i = indexFor(hash, table.length);//取得HashCode在table中的位置
5
6 //首先在數(shù)組內(nèi)根據(jù)key的HashCode找到Entry鏈表的第一個Entry
7 for (Entry<K,V> e = table[i]; e != null; e = e.next) {
8 //如果Entry的key值HashCode相同,而且具有相同的引用或邏輯相等(equals)
9 if (e.hash == hash && eq(k, e.key)) {
10 //將新的value放入Entry中,返回舊的value
11 V oldValue = e.value;
12 e.value = value;
13 e.recordAccess(this);
14 return oldValue;
15 }
16 }
17
18 //修改次數(shù)加1
19 modCount++;
20 //在table的第i個位置的鏈表后加上一個新的Entry
21 addEntry(hash, k, value, i);
22 return null;
23 }
24
25 static boolean eq(Object x, Object y) {
26 return x == y || x.equals(y);
27 }
28
*最后來看java.lang.Object的約定(經(jīng)過自己語言描述,原話自己看書去)
1.如果一個類的equals方法所用到的信息(邏輯相等的條件因素)沒有被修改的話,那么它hashCode也不會改變。換個角度來看,也就是說,hashCode返回值最好與equals方法所用到的信息相關。
2.如果兩個實例根據(jù)equals對比是相等的,那么它們的HashCode相等。
3.如果兩個實例根據(jù)equals對比是不相等的,那么它們的HashCode最好是不等,這對于Hash性能的提高有好處。
E.g 如果Person實例的ID屬性沒有被修改的話,那么它的HashCode也不會改變
1 public class Person{
2 private String ID;
3 public boolean equals(Object obj){
4 if(!(obj instanceof Person)&& ((Person)obj).getID()!=null){
5 return false;
6 }
7 return ((Person)obj).getID().equals(this.ID);
8 }
9
10 public int hashCode(){
11 if(ID==null){
12 return 23;
13 }else{
14 return ID.hashCode();
15 }
16 }
17 public String getID() {
18 return ID;
19 }
20 public void setID(String id) {
21 ID = id;
22 }
23
24 }
25
程序員的一生其實可短暫了,這電腦一開一關,一天過去了,嚎;電腦一開不關,那就成服務器了,嚎……