equals&hashCode

平时我们写一个java对象,很多时候会使用IDE自动生成equals方法和hashcode,那么这个两个方法到底有什么用?为什么两个要同时重写?如果不使用自动生成,该如何实现自己的equals和hashcode方法?

查看jdk发现equals和hashcode都是Object的方法,因此只有对象类型才能调用这两个方法:

public boolean equals(Object obj) {
    return (this == obj);
}


public native int hashCode();

equals

equals一般可认为从语义上比较两个对象是否相等,Object实现中使用==直接比较两个对象的地址是否相同,两个对象==判断的是地址是否相同,对原生类型int/double则是比较值是否相同。默认实现中对象只有equals自身才会返回true, 实际应用中我们一般是从语义层面判断两个对象是否相等,比如两个对象包含的成员之间equals都返回true,则认为两个对象equals也应该返回true, 因此,一般我们需要重写equals方法,使用业务场景中比较逻辑判定两个对象是否相同。

hashCode

hashCode返回值是int, 声明为native,在jdk中并未实现,由jvm实现者自行定义,既然是自行定义,没有规定如何实现,但是根据hashCode的作用对其返回值肯定还是有些规定的。hashCode定义了一个对象到一个int整数值得映射,主要用于hash table中作为key进行对象的定位查找。我们知道Hash Table的时间复杂度为O(1),最坏情况却为O(n),为了保证hash table的效率,应该较少碰撞的发生概率,因此hashCode函数返回值应该尽量分散,同一个对象的hashCode应该是幂等的,不同对象的hashCode应该尽量不同,但也有可能两个不同对象的hashCode相等,hashCode的实现应该尽量减少此种情况发生的概率。

equals & hashCode

一般我们看到equals和hashCode都是同时出现的,重写了equals方法的同时也要重写hashCode.为什么会这样?
你可以理解为这是java的规定,java规定objectA.equals(ObjectB) == true时,objectA.hashCode() == objectB.hashCode(),反之则不一定成立,因此重写equals后,为了保证此规则依然成立,一般也需要重写hashCode方法。

也许你还想问为什么有这样的规定?Java规范的制定者也许是从equals和hashCode的使用场景角度考虑,只有遵循这样的规定,JDK源代码中依赖这条规范的实现才合乎现实语义逻辑,如果破坏这条规则则会得到莫名奇怪的结果。

package com.test1;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;


class People{
    private String name;
    private int age;

    public People(String name,int age) {
        this.name = name;
        this.age = age;
    }  

    public void setAge(int age){
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
    }
}

public class Main {

    public static void main(String[] args) {

        People p1 = new People("A", 12);
        System.out.println(p1.hashCode());

        HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
        hashMap.put(p1, 1);

        System.out.println(hashMap.get(new People("A", 12)));
    }
}

以上代码最后打印出null,就是因为HashMap的实现依赖hashCode方法,如果我们不遵循这条规则,HashMap也就不会返回给我们预期的结果。

自定义hashCode

既然hashCode的实现应该减少碰撞,说明这是一个数学问题,在工程上有一些标准成熟的算法,这也是IDE能为我们自动生成hashCode方法实现的原因。

通过JDK源码我们可以看看各种Class的hashCode具体实现:

//Interger直接返回对应的int数
public int hashCode() {
    return value;
}

//Long则通过两个32bit异或操作强转成int
public int hashCode() {
    return (int)(value ^ (value >>> 32));
}

//String通过一个特殊的算法合并了每个字符后计算出一个Int值
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

jdk开发者使用了很好的方式实现各个类hashCode, 对我们自定义的类,则可按以下方式自定义一个合适的hashCode:

  1. 把某个非0的常数值,比如17,保存在一个名为result的int类型的变量中。
  2. 对于对象中的每个域f计算int类型hash值c:

    • 如果该域是boolean类型,则计算(f?1:0)
    • 如果该域是byte、char、short或者int类型,则计算(int)f
    • 如果该域是long类型,则计算(int)(f^(f>>>32))
    • 如果该域是float类型,则计算Float.floatToIntBits(f)
    • 如果该域是double类型,则计算Double.doubleToLongBits(f),然后重复第三个步骤。
    • 如果该域是一个对象引用,并且该类的equals方法通过递归调用equals方法来比较这个域,同样为这个域递归的调用hashCode,如果这个域为null,则返回0。
    • 如果该域是数组,则要把每一个元素当作单独的域来处理,递归的运用上述规则,如果数组域中的每个元素都很重要,那么可以使用Arrays.hashCode方法。
  3. 把上面计算得到的hash值c合并到result中

    • result = 31*result + c

参考http://www.importnew.com/8189.html

是不是很麻烦,还是自动生成吧,如果觉得自动生成的太冗长或不是想要的,可以借助Apache commons HashcodeBuilder或Guava Hash中各种HashFunction计算hashCode。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值