hashcode和equals

来源:互联网 发布:球球大作战刷圣衣软件 编辑:程序博客网 时间:2024/05/16 00:41

      在Java中,万物皆对象,所有的对象都继承于Object类,Object类有两个方法equals和hashCode。equals一般用来比较两个对象的内容是否相等,而hashCode一般用来提高容器的查询效率。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public native int hashCode();    
  2. public boolean equals(Object obj) {    
  3.   return (this == obj);    
  4. }    

      equals在没有重写的情况下和==是一样的,对于值类型,比较的是值,对于引用类型则比较的是对象的首地址。

      hashCode我们一般很少直接使用,它返回的是一个int值,在HashMap中对对象进行存储时,它会调用hashCode方法来比较两个对象是否相等。查询对象的时候也会调用hashCode以提高查询效率。

      一般来说equals方法比较相等,则hashCode一定相等,反过来不一定成立,因为具有相同的hashCode不一定是相同的对象。一个好的hashCode函数应该能做到为不同的对象产生不相等的hash值。


      如果我们对equals方法进行重写时,一般强烈建议对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。因为我们在使用HashMap、HashSet的时候会使用hashCode和equals来判断存入的是否是同一个对象。如果不重写hashCode,那么会继承Object中的,它返回的是一个对象的地址,对于两个对象,这个地址是永远不会相等。如果hashCode都不相等,就不会再调用equals方法进行比较了。

      当从HashSet集合中查找某个对象时,java系统首先会调用对象的hashCode()方法来获得该对象的哈希码,然后根据哈希码找到对应的存储区域,最后取得该存储区域内的每个元素与该对象进行equals方法比较。这样就不用遍历集合中的所有元素就可以得到结论,可见HashSet集合具有很好的对象检索性能。


      下面我们通过几个例子,演示一下对象重写或不重写hashCode与equals能否被存入HashSet中:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class MyObject {  
  2.     public int x;  
  3.     public int y;  
  4.   
  5.     public MyObject(int x, int y) {  
  6.         this.x = x;  
  7.         this.y = y;  
  8.     }  
  9.   
  10.     @Override  
  11.     public int hashCode() {  
  12.         final int prime = 31;  
  13.         int result = 1;  
  14.         result = prime * result + x;  
  15.         result = prime * result + y;  
  16.         return result;  
  17.     }  
  18.   
  19.     @Override  
  20.     public boolean equals(Object obj) {  
  21.         if (this == obj)  
  22.             return true;  
  23.         if (obj == null)  
  24.             return false;  
  25.         if (getClass() != obj.getClass())  
  26.             return false;  
  27.   
  28.         final MyObject myObject = (MyObject) obj;  
  29.         if (x != myObject.x || y != myObject.y) {  
  30.             return false;  
  31.         }  
  32.         return true;  
  33.     }  
  34. }  

测试1: MyObject类重写了父类Object中的hashCode和equals方法,如果两个MyObject对象的x y值相等的话,那么他们的hashCode的值就会相等,equals后返回true,测试代码如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         HashSet<MyObject> set = new HashSet<MyObject>();  
  4.         MyObject r1 = new MyObject(33);  
  5.         MyObject r2 = new MyObject(55);  
  6.         MyObject r3 = new MyObject(33);  
  7.         set.add(r1);  
  8.         set.add(r2);  
  9.         set.add(r3);  
  10.         set.add(r1);  
  11.         System.out.println("size:" + set.size());  
  12.     }  
  13. }  

我们向HashSet中存入了4个对象,打印set集合大小为size:2,为什么为2?因为我们重写了MyObject类的hashCode方法,只要MyObject对象的x,y属性值相等,那么它的hashCode值就是相等的。所以先比较hashCode的值,r1和r2对象的x y属性值不等,那么hashCode就不等,所以r2对象可以放进去。r3对象的x y属性值和r1对象的属性值相同,所以hashCode是相等的,然后再比较r1和r3的equals方法,也是相等,所以r1、r3对象时相等的,所以r3不能放进去。最后一个r1肯定也是放不进去的。


测试2:把MyObject对象的hashCode方法注释,即不重写Object对象的hashCode方法,再运行一下代码

运行结果:size:3

因为hashCode方法没有被重写,使用Object中的hashCode方法返回的是对象的地址,不同的实例对象的hashCode是不同的,所以hashset中可以存入r1,r2,r3


测试3:把MyObject对象中的equals方法注释掉,直接返回false,不注释hashCode方法,运行一下代码:

运行结果:size:3

这个结果让人比较意外,首先r1和r2对象比较hashCode不相等,那么r2放入hashset中。再来看一下r3,比较r1和r3的hashCode方法,是相等的,然后比较他们的equals方法,因为equals始终返回false,所以r1和r3也是不相等的,所以r3可以放入set。再看最后一个r1(为防混淆,我们称它为r4吧),r1和r4 hashCode相等,再比较equals返回false,所以r1和r4不相等,同理r2和r4,r3和r4也不相等,所以r4应该可以放入集合中,那为什么集合的大小是3呢?

我们有必要翻一下HashSet的源码了:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public boolean add(E e) {    
  2.         return map.put(e, PRESENT)==null;    
  3. }    
我们会发现HashSet是基于HashMap实现的,我们打开HashMap的put方法:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public V put(K key, V value) {  
  2.     if (key == null)  
  3.         return putForNullKey(value);  
  4.     int hash = hash(key);  
  5.     int i = indexFor(hash, table.length);  
  6.     for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  7.         Object k;  
  8.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  9.             V oldValue = e.value;  
  10.             e.value = value;  
  11.             e.recordAccess(this);  
  12.             return oldValue;  
  13.         }  
  14.     }  
  15.   
  16.     modCount++;  
  17.     addEntry(hash, key, value, i);  
  18.     return null;  
  19. }  
我们主要看一下if中的判断:

if(e.hash == hash && ((k = e.key) == key || key.equals(k)))

首先是判断hashCode是否相等,不相等的话,直接跳过,相等的话,再来比较这两个对象是否相等或者这两个对象的equals方法,因为进行的是“ 或 ” 操作,所以只要有一个成立即可,那这里我们就可以解释了,其实上面的那个集合的大小是3, 因为最后的一个r1没有放进去,因为(k = e.key) == key 即 r1==r1返回true就return了,所以没有放进去了。集合的大小是3,如果我们将hashCode方法设置成始终返回false的话,这个集合就是4了。

0 0