Java中的equals()和hashCode()方法

    在Java语言中,一般不需要自定义equals()和hashCode()这两个方法,当需要对对象的内容进行比较的时候,才需要这样两个方法。例如,需要把对象放入HashSet或者把对象作为key放入HashMap、 Hashtable时,如果不想把具有相同内容的两个对象作为两个对象来看待,就需要重定义这两个方法。这两个方法的使用是紧密配合的,要是设计了其中一个,就要设计另外一个。

    equals():
    用于两个对象的比较,在Object类中已经实现了这个方法,是对对象内部地址的比较,即如果两个对象的内部地址是一样的则是相等的。如果要按照对象内容的进行比较,就需要重载这两个方法。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”。
    hashCode():
    这个方法返回的是一个用来进行hash操作的整型数,可用于在Collection对象中定位特定对象的位置。Object中默认的实现是把对象内部地址转化为整数作为hashCode。
    hashCode()的返回值和equals()的关系如下:
  • 如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。
  • 如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。
    这里有必要说明一下HashMap的原理。为了优化查找对象的性能,在HashMap中按照键值对象的hash值放了若干个箱子,当有一个键值对象加入进来时,调用键值对象的hashCode()方法,根据计算出的hash值把对象放入对应的箱子。当对键值对象进行查找时,首先计算对象的hash值,找到对应的箱子,然后调用equals()与箱子中的对象逐个比较,直到找出相等的对象或者遍历了一遍。
    如果x.equals(y)返回“true”,而x.hashCode() != y.hashCode()会有什么后果呢?假设把x、y方法HashMap,即hashMap.put(x, xValue)和hashMap.put(y, yValue),在hashMap中会存在两个元素x、y,而不是一个。原因很好理解,因为二者的hashCode不同,在hashMap中会放入不用的箱子,从而会被认为是两个对象。
    使用hashCode的目的是为了把对象散列在不同的地方,从而提高检索对象的性能,所以编写一个好的hashCode算法相当重要。在Effective Java 2 edition 中给出了一个简单的hashCode算法:
    1、保存一些常量非0值,例如17,在一个int的result变量中。这个值可以是任意的,但是最好不是0,会增加冲突的可能。
    2、对象每一个重要的域f(就是equals方法使用到那些field)做以下操作:
        a)对域f计算一个int的哈希码c
           i.如果field是boolean型的,计算(f ? 1 : 0)
           ii.如果field是byte,char,int之类的,计算 (int)f
           iii.如果field是long, 计算 (int) (f^(f>>32))
           iv.如果field是float,计算 Float.floatToIntBits(f)
           v.如果field是double,计算Double.doubleToLongBits(f),然后使用2.a.iii的方法计算hash code
           vi.如果field是一个对象的引用,并且这个类的此域比较的equals方法使用了递归的调用equals,直接递归的调用hashCode方法。如果一个更复杂的比较是必须的,对此域计算”canonical representation(规范的表示)”并且在”规范的表示”上调用hashCode(invoke hashCode on the canonical representation)。如果此field为null,返回0。
           vii.如果field时array,将数组中的每个元素作为一个单独的field,用上述规则来计算,并使用2.b中的方式合并这些值。如果每个元素都是significant,你可以使用1.5版本添加的Arrays.hasCode方法。
         b)合并2.a中哈希码c的值到result使用下面的方法:
                   result=31*result+c;
    3、返回result
    4、当你完成hashCode函数之后,自我检验一下相等的实例是否有相等的hash code。编写单元测试去检验你所想的结果。如果相等实例有不同的hash code,找出原因并解决。
    下面给出一个例子,利用上面讲述的东西应该可以解释是什么原因。代码如下:
package learning.collection;

import java.util.HashMap;
import java.util.Map;

/**
 * 测试equals()、hashCode()方法对对象在Collection中的影响。
 * 
 */
public class EqualsAndHashCode
{

    /**
     * @param args
     */
    public static void main(String[] args)
    {
        Point p1 = new Point(1,2);
        Point p2 = new Point(1,2);
        System.out.println("p1.equals(p2):" + p1.equals(p2));
        System.out.println("p1.hashCode():" + p1.hashCode());
        System.out.println("p2.hashCode():" + p2.hashCode());
        Map<Point, String> map1 = new HashMap<Point, String>();
        map1.put(p1, "point 1");
        map1.put(p2, "point 2");
        System.out.println("map1.size():" + map1.size());
        
        BadPoint bp1 = new BadPoint(1,2);
        BadPoint bp2 = new BadPoint(1,2);
        System.out.println("bp1.equals(bp2):" + bp1.equals(bp2));
        System.out.println("bp1.hashCode():" + bp1.hashCode());
        System.out.println("bp2.hashCode():" + bp2.hashCode());
        Map<BadPoint, String> map2 = new HashMap<BadPoint, String>();
        map2.put(bp1, "point 1");
        map2.put(bp2, "point 2");
        System.out.println("map2.size():" + map2.size());
        
        GoodPoint gp1 = new GoodPoint(1,2);
        GoodPoint gp2 = new GoodPoint(1,2);
        System.out.println("gp1.equals(gp2):" + gp1.equals(gp2));
        System.out.println("gp1.hashCode():" + gp1.hashCode());
        System.out.println("gp2.hashCode():" + gp2.hashCode());
        Map<GoodPoint, String> map3 = new HashMap<GoodPoint, String>();
        map3.put(gp1, "point 1");
        map3.put(gp2, "point 2");
        System.out.println("map3.size():" + map3.size());
    }

}

/**
 * 没有重载equals()、hashCode()方法。
 */
class Point 
{
    private int x;
    private int y;
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    
    public int getX()
    {
        return this.x;
    }
    
    public int getY()
    {
        return this.y;
    }
}

/**
 * 重载了equals()方法,导致equals()返回true,而hashCode不同。
 * 这是一个不好的设计(甚至是错误的)。
 */
class BadPoint
{
    private int x;
    private int y;
    public BadPoint(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    
    public int getX()
    {
        return this.x;
    }
    
    public int getY()
    {
        return this.y;
    }
    
    @Override
    public boolean equals(Object obj)
    {
        if (obj == null) return false;
        if (!(obj instanceof BadPoint)) return false;
        BadPoint objPoint = (BadPoint) obj;
        if (this.x == objPoint.x && this.y == objPoint.y) return true;
        return false;
    }
}

/**
 * 重载了equals()、hashCode()方法。
 */
class GoodPoint
{
    private int x;
    private int y;
    public GoodPoint(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    
    public int getX()
    {
        return this.x;
    }
    
    public int getY()
    {
        return this.y;
    }
    
    @Override
    public boolean equals(Object obj)
    {
        if (obj == null) return false;
        if (!(obj instanceof GoodPoint)) return false;
        GoodPoint objPoint = (GoodPoint) obj;
        if (this.x == objPoint.x && this.y == objPoint.y) return true;
        return false;
    }
    
    @Override
    public int hashCode()
    {
        int result = 17;
        result = 31 * result + this.x;
        result = 31 * result + this.y;
        return result;
    }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值