好吧好吧,我承认这有点标题党的嫌疑,我这不是隔太久没更新,有点兴奋么。
板砖拍够了,臭鸡蛋扔够了,别来打酱油便行了。我这就进入正题。其实正确的标题应该叫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 //最近,麦当当推出了麦香翅,味道不是一般的好,那是相当的好。
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 //山寨小食店苦思瞑想,终于发现了问题,原来他们指打相同的名号是不行的。
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[]的数组来存取,table的每个元素是一个链表结构,链表的每个元素称为 Entry<key,value>,是真正存放key和value的对象。在put的过程中,HashMap会通过特定的哈希算法将key对象的hashCode对应到table的某个索引下,然后再用key对比链表中每个Entry.key,如果key相同则更新value。否则就加入新的 Entry到链表中。
HashMap的数据结构图:
再看一段HashMap的源代码:
1 public V put(K key, V value) {
2 K k = maskNull(key);// 如果key为null则使用缺省的Object
3 int hash = hash(k);//将k的hashCode经过一定的计算得到新的HashCode
4 int i = indexFor(hash, table.length);//取得HashCode在table中的位置
5
6 //首先在数组内根据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 //修改次数加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的约定(经过自己语言描述,原话自己看书去)
1.如果一个类的equals方法所用到的信息(逻辑相等的条件因素)没有被修改的话,那么它hashCode也不会改变。换个角度来看,也就是说,hashCode返回值最好与equals方法所用到的信息相关。
2.如果两个实例根据equals对比是相等的,那么它们的HashCode相等。
3.如果两个实例根据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
程序员的一生其实可短暂了,这电脑一开一关,一天过去了,嚎;电脑一开不关,那就成服务器了,嚎……