欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

equals&hashCode

程序员文章站 2022-03-15 20:47:37
...

平时我们写一个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。

相关标签: hashCode

推荐阅读