hashCode()拾遗

          此前一位同事问我为何需要重写hashCode(),而且为什么说重写equals()同时也应该hashCode(),下面我就对hashCode()及equals()作一下介绍。
          一。相等性 equals方法
          Object的equals()方法内容如下:
public   boolean  equals(Object obj)  return (this == obj); }
可以看出默认的相等性是比较内存位置是否相等。但对于自定义类,我们一般都会重写equals()方法,以实现语义相等性。例如,对于类Student
public   class  Student {
private String number;
private String name;

//filed setter/getter method
}

我们的equals()方法如下:
public   boolean  equals(Object object)  {
        
if (object == null{
            
return false;
        }

        
if(!(object instanceof Student)) {
            
return false;
        }

        Student that 
= (Student)object;
        
return this.number.equals(that.number) && this.name.equals(that.name);
    }
        从以上重写 equals()方法应该理解到对象内容的比较才是设计equals()的真正目的。
        并且Java语言对equals()的要求如下,这些要求是必须遵循的。
  • 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
  • 反射性:x.equals(x)必须返回是“true”。
  • 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
  • 还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
  • 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
      二。集合与相等性 hasCode()方法
      看看以下代码:
    Student A  =   new  Student( " 001 " , " yangqiang " );
    Student sameA 
=   new  Student( " 001 " , " yangqiang " );
    
    Map
< Student, String >  map  =   new  HashMap < Student, String > ();
    map.put(A, 
"" );
    
boolean  isConKey  =  map.containKey(sameA);
    乍一看,isConKey 应该为true,其实不然,原因出在哪里呢?
    下面就来分析分析,
HashMap基于哈希表结构进行存储和获取数据。而现在的hashCode()方法是默认的Object中的HashCode方法返回的HashCode对应于当前 的地址,也就是说对于不同的对象,即使它们的内容完全相同,用HashCode()返回的值也会不同。这样实际上违背了我们的意图。所以返回的 isConKey 应该为false,这下应该就清楚原因了吧,哈哈。
     知道原因之后,我们回到我们在使用 HashMap时,  希望利用相同内容的对象索引得到相同的目标对象的问题上,这就需要我们重写HashCode()方法。
     在重写HashCode()方法之前,我们需要知道:
      (1)Object中对hashCode的约定:
  1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
  2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
  3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

      (2)hashCode()的返回值和equals()的关系如下:
         1.如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。
         2.如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

      (3)Hash冲突。

  常见的Hash冲突是不同key对象最终产生了相同的索引,而一种非常甚至绝对少见的Hash冲突是,如果一组对象的个数大过了int范围,而 HashCode的长度只能在int范围中,所以肯定要有同一组的元素有相同的HashCode,这样无论如何他们都会有相同的索引.当然这种极端的情况 是极少见的,可以暂不考虑,但是对于同的HashCode经过取模,则会产中相同的索引,或者不同的对象却具有相同的HashCode,当然具有相同的索 引。

         基于以上,一个理想的哈希算法应该是自定义类属性域的算术组合,并且哈希值运算使用质数,因为对哈希长度进行求余运算时可以得到较好的分布,减少哈希冲突。以下为示例:

public   int  hashCode() 
    
int hash = 1;  //任意质数
    hash 
= hash * 31 + someNonNullField.hashCode();
    hash 
= hash * 31 
                
+ (someOtherField == null ? 0 : someOtherField.hashCode());
    
return hash;
  }
      应用上面的算法重写Student的hashCode()方法,重新运行 boolean  isConKey  =  map.containKey(sameA);

isConKey 返回true,哈哈。

     好了,总结一下,如果为某个类编写了equals方法,那么同时应该编写hashCode方法,如果不编写hashCode方法,在使用基于哈希表结构的集合时就会发生意想不到的结果,为了避免这些问题,你应该谨记这条最佳实践。






 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值