Java Hash集合的equals()与hashCode() 方法

 

  Java 集合实现类,无论是HashSet、HashMap等所有的Hash算法实现的集合类(后面简称Hash集合),加入的对象必须实现 hashCode() 与 equals() 方法,稍微不同的地方是:HashSet 需要对整个对象实现两个方法,而HashMap 只需要对作为key的对象实现这两个方法。因为向Hash集合存入一个元素时,Hash集合会调用该对象的hashCode()方法来得到该对象的hashCode的值,然后根据hashCode的值决定该对象在Hash集合中存储位置。如果两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,Hash集合将会把它们视为不同的对象,两个元素都可以添加到Hash集合里。所以Hash集合判断两个元素是否相等的标准是:两个对象通过equals()方法比较相等,并且两个元素的hashCode()方法返回值也相等。

 

重写hashCode()方法的规则

① 同一个对象多次调用hashCode()方法返回值相同

② 两个对象通过equals()方法比较返回true 时,这两个对象的hashCode()方法返回相等的值

③ 对象中用于equals() 方法比较标准的变量,都应该用于计算hashCode值

 

重写hashCode方法一般步骤

① 把对象内每个有意义的变量计算出一个int 型的hashCode值

② 用第1步计算出来的多个hashCode值组合成一个hashCode值返回

③ 为了避免直接相加产生偶然相等,可以通过为各变量的hashCode值乘以任意质数再相加

 

案例:

① 首先来看一个没有重写equals() 与 hashCode() 方法的对象,添加到HashSet 时会出现什么情况。

我们创建一个Person类,包含两个成员变量,id 与 name,如果两个对象的这两个属性都不等我们才认为它们是不同的对象,我们没有重写equals() 与 hashCode() 方法,但是我们希望HashSet 集合对同一个对象只能保存一份,有相同对象加入时,加入失败。

 1 class Person
 2 {
 3     private String name;
 4     private int id; 
 5     
 6     public Person()
 7     {}
 8     
 9     public Person(int id,String name)
10     {
11         this.id = id;
12         this.name = name;
13     }
14     
15     public int getId() {
16         return id;
17     }
18 
19     public void setId(int id) {
20         this.id = id;
21     }
22 
23     public String getName() {
24         return name;
25     }
26     public void setName(String name) {
27         this.name = name;
28     }
31 }

 

 1         
 2         HashSet haSe = new HashSet();
 3         
 4         //相同id与name的添加两次
 5         haSe.add(new Person(1001, "latiny"));
 6         haSe.add(new Person(1001, "latiny"));
 7         
 8         //遍历HashSet
 9         for(Object obj:haSe)
10         {
11             Person p = (Person)obj;
12             System.out.println(p.getId()+" "+p.getName()+" "+p.hashCode());
13         }
14         

  输出结果:

1001 latiny 355165777
1001 latiny 1807500377

可以看到name与id相同的两个对象都成功的加入到了HashSet集合,因为它们的hashCode值不一样,这显然没有达到预期的效果,如果要实现预期的效果需要重写Person类 equals() 与 hashCode() 方法。

 

② 重写Person类equals() 与 hashCode() 方法

在上面的Person类添加如下代码:

 1     //重写hashCode() 方法
 2     public int hashCode() {
 3         int hash = 7;
 4         hash = hash*31 + this.id*11 + this.name.hashCode()*31;
 5         return hash;
 6     }
 7 
 8     //重写equals() 方法
 9     public boolean equals(Object obj) {
10         if(this==obj)
11         {
12             return true;
13         }
14         //如果两个对象的id与name都相等,则两个对象相等
15         if(obj.getClass()==Person.class)
16         {
17             Person p=(Person)obj;
18             return this.id==p.id && this.name==p.name;
19         }
20         else
21         {
22             return false;
23         }
24     }

  

  重新执行代码:

 1         
 2         HashSet haSe = new HashSet();
 3         
 4         //相同id与name的对象添加两次
 5         haSe.add(new Person(1001, "latiny"));
 6         System.out.println(haSe.add(new Person(1001, "latiny")));
 7         
 8         //遍历HashSet
 9         for(Object obj:haSe)
10         {
11             Person p = (Person)obj;
12             System.out.println(p.getId()+" "+p.getName()+" "+p.hashCode());
13         }
14         

 

  输出结果: 

false
1001 latiny -46445433

可以看到 id与 name 相同的两个对象只有一个加入到了HashSet集合,第二次添加时失败因为我们重写了equals() 与 hashCode() 方法,只要Person类对象id与name都相同,equals()返回true,hashCode() 返回值相同,HashSet集合就会将这两个对象视为同一个对象。

 

HashMap 的使用与HashSet类似,作为key的对象也需要重写 equals() 与 hashCode() 方法,不然也会出现将相同对象作为key值成功插入到HashMap中。

① 未重写 equals() 与 hashCode() 方法

 1 class Person
 2 {
 3     private String name;
 4     private int id; 
 5     
 6     public Person()
 7     {}
 8     
 9     public Person(int id,String name)
10     {
11         this.id = id;
12         this.name = name;
13     }
14     
15     public int getId() {
16         return id;
17     }
18 
19     public void setId(int id) {
20         this.id = id;
21     }
22 
23     public String getName() {
24         return name;
25     }
26     public void setName(String name) {
27         this.name = name;
28     }
29 
30 }

  

  将Person类作为HashMap的key

 1         
 2         HashMap<Person, String> hash = new HashMap<Person, String>();
 3         hash.put(new Person(1, "latiny"), "周瑜");
 4         hash.put(new Person(1, "latiny"), "曹操");
 5         hash.put(new Person(1, "latiny"), "刘禅");
 6         
 7         //foreach遍历HashMap
 8         for(Object key:hash.keySet())
 9         {
10             String name = hash.get(key);
11             System.out.println(key+"-->"+name+" "+key.hashCode());
12         }
13         
14         Object obj = hash.get(new Person(1, "latiny"));
15         String name = (String) obj;
16         System.out.println(name);

  输出结果:

com.latiny.set.Person@544a5ab2-->刘禅 1414159026
com.latiny.set.Person@152b6651-->曹操 355165777
com.latiny.set.Person@6bbc4459-->周瑜 1807500377
null

可以看到,id 与 name 相同的Person对象作为 key 重复添加到HashMap集合中。

更为严重的问题是,当我们想要以相同的 id 与 name的Person对象作为key去获取value 时,结果竟然是null,为什么呢?因为这个Person对象的id 与 name 与之前的三个对象相同,但是在内存中它却是一个新的对象,有自己独立的空间,即有自己独立的hashCode值,由于我们没有重写hashCode方法,它的hashCode计算方法还是按照Object这个类来实现的。

 

② 重写 equals() 与 hashCode() 方法之后

 1 class Person
 2 {
 3     private String name;
 4     private int id; 
 5     
 6     public Person()
 7     {}
 8     
 9     public Person(int id,String name)
10     {
11         this.id = id;
12         this.name = name;
13     }
14     
15     public int getId() {
16         return id;
17     }
18 
19     public void setId(int id) {
20         this.id = id;
21     }
22 
23     public String getName() {
24         return name;
25     }
26     public void setName(String name) {
27         this.name = name;
28     }
29 
30     
31     //重写hashCode() 方法
32     public int hashCode() {
33         int hash = 7;
34         hash = hash*31 + this.id*11 + this.name.hashCode()*31;
35         return hash;
36     }
37 
38     //重写equals() 方法
39     public boolean equals(Object obj) {
40         if(this==obj)
41         {
42             return true;
43         }
44         //如果两个对象的id与name都相等,则两个对象相等
45         if(obj.getClass()==Person.class)
46         {
47             Person p=(Person)obj;
48             return this.id==p.id && this.name==p.name;
49         }
50         else
51         {
52             return false;
53         }
54     }
55     
56 }

 

  执行测试代码

 1         
 2         HashMap<Person, String> hash = new HashMap<Person, String>();
 3         hash.put(new Person(1, "latiny"), "周瑜");
 4         hash.put(new Person(1, "latiny"), "曹操");
 5         hash.put(new Person(1, "latiny"), "刘禅");
 6         
 7         //foreach遍历HashMap
 8         for(Object key:hash.keySet())
 9         {
10             String name = hash.get(key);
11             System.out.println(key+"-->"+name+" "+key.hashCode());
12         }
13         
14         Object obj = hash.get(new Person(1, "latiny"));
15         String name = (String) obj;
16         System.out.println(name);
17         

  输出结果:

com.latiny.set.Person@fd3b218f-->刘禅 -46456433
刘禅

可以看到,最后一次添加的元素将之前添加的都覆盖了,因为我们重写的方法判断如果Person类的id与name相同,equals()返回true, hashCode() 返回相同的值,HashMap认定它们key值相同,会将之前加入的元素覆盖。我们也可以将具有相同的id与name的Person类作为key值去访问value。

 

 

posted @ 2018-01-26 13:34  Latiny  阅读(3045)  评论(0编辑  收藏  举报