大话深入浅出Effective Java核心实战编程思想之——那些鸡翅

本文通过一个生动的例子解释了在Java中实现equals方法时为何必须同时重写hashCode方法。通过鸡翅的故事,揭示了HashMap的工作原理及如何通过重写hashCode确保正确的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

好吧好吧,我承认这有点标题党的嫌疑,我这不是隔太久没更新,有点兴奋么。
    板砖拍够了,臭鸡蛋扔够了,别来打酱油便行了。我这就进入正题。其实正确的标题应该叫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到链表中。

<meta name="ProgId" content="Word.Document"> <meta name="Generator" content="Microsoft Word 11"> <meta name="Originator" content="Microsoft Word 11"> <link rel="File-List" href="file:///C:%5CDOCUME%7E1%5CADMINI%7E1%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">

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  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值