Java基础之 与equals()如影随行的hashCode()

Java基础之 与equals()如影随行的hashCode()

  上一篇文章我们谈到了equals()的重写,接下来我们说说与equals()关系紧密的hashCode(),这从标题就可窥见一斑。

  hashCode()返回该对象的哈希码值,该值通常是一个由该对象的内部地址转换而来的整数,它的实现主要是为了提高哈希表(例如java.util.Hashtable提供的哈希表)的性能。在每个重写了equals方法的类中,你必须也要重写hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法与所有基于散列值(hash)的集合类结合在一起正常运行。

下面是hashCode约定的内容,来自java.lang.Object的规范:

l 在一个Java应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同。

l 如果两个对象根据equals(Object)方法是相等的,那么对这两个对象中的每一个对象调用hashCode方法都必须生成相同的整数结果。

l 如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。


  下面我们仍然用上一篇文章“(override)重写equals()方法”中所用的例子,来详细解释上面约定的内容:

public class ColorPoint{

  private Point point;

  private Color color;

  public ColorPoint(int x, int y, Color color){

    point = new Point(x, y);

    this.color = color;

  }

  // 返回一个与该有色点在同一位置上的普通 Point 对象

  public Point asPoint(){

    return point;

  }

  public boolean equals(Object o){

    if(o == this)

      return true;

    if(!(o instanceof ColorPoint))

      return false;

    ColorPoint cp = (ColorPoint)o;

    return cp.point.equals(point)&&
              cp.color.equals(color);

  }

}

假设你试图将这个类与 HashMap 一起使用:

public class Test{

   public static void main(String[] args){

     Map m = new HashMap();

     m.put(new ColorPoint(1,2,Color.RED),"red");

     System.out.println(m.get(

       new ColorPoint(1,2,Color.RED)));

   }

}

  这时候,你可能会期望程序返回并输出" red" ,但是实际的运行结果是: null 。为什么呢?因为这里涉及到两个 ColorPoint 实例:第一个被用于插入到 HashMap 中,第二个实例与第一个相等,被用于 ( 试图 ) 检索。由于 ColorPoint 类没有重写 hashCode 方法,从而导致两个相等的实例具有不相等的散列码,违反了 hashCode 的约定。因此,要想解决这个问题 , 只需为 ColorPoint 类提供一个适当的 hashCode 方法既可。

  如何编写一个适当的 hashCode 方法呢?下面是我从《 Effective Java 》一书中摘抄而来的“诀窍”:

1. 把某个非零常数值,比如说 17 ,保存在一个 int 类型的变量 result 中。

2. 对于对象中每个关键域 f( equals 方法中考虑的每个域 ) ,完成以下步骤:

A. 为该域计算 int 类型的散列码 c:

a. 如果该域是 boolean 类型,则计算 (f ? 0 : 1)

b. 如果该域是 byte char short int 类型,则计算 (int)f

c. 如果该域是 long 类型,则计算 (int)(f ^ (f >>> 32))

d. 如果该域是 float 类型,则计算 Float.floatToIntBits(f)

e. 如果该域是 double 类型,则计算 Double.doubleToLongBits(f) 得到一个 long 类型的值,然后按照步骤 2.A.c ,对该 long 型值计算散列值。

f. 如果该域是一个对象引用,并且该类的 equals 方法通过递归调用 equals 的方式来比较这个域,则同样对该域递归调用 hashCode

g. 如果该域是一个数组,则把每一个元素当做一个单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算散列值。

B. 按照下面的公式,把步骤 A 中计算得到的散列码 c 组合到 result :

result = 37*result + c;

3. 返回 result

4. 写完了 hashCode 方法之后,问自己“是否相等的实例具有相等的散列码”。如果不是的话,请找出原因,并修正错误。


  现在,我们把这种方法用到 ColorPoint 类中。它有两个关键域,都是对象引用类型。根据上面的步骤,很直接地会得到下面的散列函数:

public int hashCode(){

   int result = 17;

   result = 37*result + point.hashCode();

   result = 37*result + color.hashCode();

   return result;

}

接下来还要为 Point 类重写 hashCode 方法,它的两个关键域都是 int 类型,添加如下代码:

public int hashCode(){

   int result = 17;

  result = 37*result + x;

  result = 37*result + y;

  return result;

}

  最后,我们再次运行上面的 Test 类,看看实际的运行结果是否和我们期望的值一样。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值