为什么重写equals方法一定也要重写hashCode?

重写 equals() 首先明确你的目的

首先我强调一下,没事别去重写eqauls()方法,除非真的需要。
很多时候,我们需要判断两个对象是否相等,比如说一个等腰三角形类:
triangle

public class Triangle{
    private double A; //底边长
    private double height;
    private double area;
  ............
}

假设我们认为两个面积相等的等腰三角形,那么这两个等腰三角形就是同一个对象。
那么这时候我们就需要去重写它的equals()方法了 。

equals约定

重写equals方法 要遵循以下几点:

  • 自反性(reflexive)。对于任意不为null的引用值x,x.equals(x)一定是true。
  • 对称性(symmetric)。对于任意不为null的引用值x和y,当且仅当x.equals(y)是true时,y.equals(x)也是true。
  • 传递性(transitive)。对于任意不为null的引用值x、y和z,如果x.equals(y)是true,同时y.equals(z)是true,那么x.equals(z)一定是true。
  • 一致性(consistent)。对于任意不为null的引用值x和y,如果用于equals比较的对象信息没有被修改的话,多次调用时x.equals(y)要么一致地返回true要么一致地返回false。
  • 对于任意不为null的引用值x,x.equals(null)返回false。

我们很多时候可能就是直接这么写的:

@Override
public boolean equals(Object anObject){
     if (this == anObject) {  
        return true;  
    }  
    if (anObject instanceof Triangle) {  
        Triangle anotherTriangle = (Triangle)anObject;  
        return Double.compare(this.area,anotherTriangle.area)==0}
    return false;
}

这么看来,equals这个方法本身并没有什么问题。但是如果放在hashSet里它就有问题了。
比如以下情况:

Triangle t1 = new Triangle("6","4","12");
Triangle t2 = new Triangle("6","4","12");
Set<Triangle> tempSet = new HashSet()<Triangle>;
tempSet.add(t1);
tempSet.add(t2);

原因

HashSet 集合是不会出现重复的。对于t1和t2来说,在我们看来他们应该是同一个三角形。但是结果肯定tempSet里会存在t1和t2。
这是因为在HashSet里它add方法是这样的

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

然后看map的put方法:

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

看hash方法:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

在放入key的时候调用了hash()方法,也就是说用的Triangle的hash方法,Triangle的hash方法继承自Object。表面上HashSet 是没有key,但从代码可以看出,他是把传入的数据hash后作为key的。Object的hash方法是native方法,不同对象返回的hash值不同。刚刚传入的两个对象实际上是地址不同的对象,所以它们的hash值肯定是不同的。所以如果我们只要重写hashCode方法,针对相同面积的三角形,返回相同的值就行。

@Override
public int hashCode(){
    int result = Double.hashCode(area);
    result = 31 * result + Double.hashCode(A);
    result = 31 * result + Double.hashCode(height);
    return result;
}

这里用31的原因是因为31在编译器优化时会用位移和减法代替乘法可以得到更好的性能,Objects 类有一个静态方法可以提供同样的功能,但是效率稍差。原因是它会创建数组。在性能要求不高的情况下,可以用它,很方便:

@Override
public int hashCode(){
   return Objects.hash(area,A,height);
}

可以看下Objects方法里实现:

    public static int hash(Object... values) {
        return Arrays.hashCode(values);
    }

不过因为例子的原因,其实这上面我刚刚的写法可能是有问题的。Effective Java中说过

如果一个域的值可以根据参与计算的其他域值计算出来,则可以把这个域排除在外。

我这里关键域是area,但是它确实可以通过底边和高求出。但千万不要为了效率而排出关键域(就是eqauls对比的字段)。不然肯定会出乱子。
上面可能会违反hashCode约定的第二条。这里只是为了方便理解。还希望不要效仿。。。

  1. 在应用程序中,只要对象的euqals方法的比较操作所用的信息没有修改,那么对于同一个对象的调用多次hashCode,必须始终如一返回同一个哈希值。
  2. 如果两个对象通过equals比较相等,那么它们的哈希值相同。
  3. 如果两个对象通过euqals比较不等,他们的哈希值可能相同,取决于hashCode的实现,由此散列表的性能也会有区别。

其实这也再一次告诉我们,重写equals方法时,必须要重写hashCode方法。
这里推荐AutoValue ,它可以帮你自动生成hashCode方法。部分IDE也支持自动生成。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值