深入理解HashCode和equals

hashCode和equals方法

JDK version:1.8.0_171


Object类位于Java的顶层,是Java所有类的父类。任何对象,包括数组,都实现了该类的方法。首先针对Object类中的两个方法equals()和hashCode()方法进行讲解。

1. Object类hashCode和equals方法

hashCode方法返回对象的哈希值,实现本方法主要是为了支持hash表(比如HashMap
对于hashCode的规定如下:

  1. 在Java应用程序的一次执行过程中,如果一个对象在equals()方法进行比较所使用到的信息没有修改,那么该对象的hashCode()不管调用多少次,始终返回相同的hash值。但多次运行Java应用程序时,同一个对象的hashCode()方法的返回结果可以不同。
  2. 如果x.equals(y)返回true,那么x和y的hashCode()方法应该返回相同的hash值。
  3. 如果x.equals(y)返回false,那么x和y的hashCode()方法可以返回相同的hash值,但是我们必须意识到对于"不相等"的两个对象返回相同的hash值,这可能会降低hash表的性能。
public native int hashCode();

  在合理可行的范围内,Object类实现的hashCode方法对于不同的对象返回不同的整数。
修饰符native表明该方法是原生方法,由操作系统实现,Java只负责调用,不同平台可能会有不同的实现,这里可以理解为Object类的hashCode()返回的hash值是利用内存地址经过算法得来的(本机对于使用集成IDE实测,同一个程序中的同一个对象object,其hashCode返回始终相同,命令行多次运行结果也相同,但IDE和命令行对于同一程序中的object的hashCode返回不同,原因不太懂)。

equals方法表明与其他对象是否相等,实现了非空对象 引用的等价关系:

  1. 自反性:对于任何非空引用值x,x.equals(x)应返回true;
  2. 对称性:对于任何非空引用值x和y,x.equals(y)返回true当且仅当y.equals(x)返回true;
  3. 传递性:对于任何非空引用值x,y和z,如果x.equals(y)返回true且y.equals(z)返回true,则x.equals(z)也返回true;
  4. 一致性:对于任何非空引用值x和y,如果不改变equals比较所使用到的信息,则不管调用x.equals(y)多少次,始终返回true或者false;
  5. 对于非空引用值x,x.equals(null)应返回false。
    Object类实现的equals方法实现了对象间最具辨别性的等价关系,即对于任何非空引用值x和呀,当且仅当x和y引用的是同一个对象(即x == y,直接比较x和y在内存中的地址,这往往没什么用)。
    public boolean equals(Object obj) {
	    //即直接比较内存地址
        return (this == obj);
    }

当重写equals()方法时往往需要修改hashCode()方法,来保证"相等"的两个对象具有相同的hash值。

2.Java包装类中hashCode和equals方法

Java中的8种包装数据类型均重写了这两个方法,下面看一些实现。

  • Integer类的hashCode直接返回对应的值value:
    @Override
    public static int hashCode(int value) {
        return value;
    }

equals直接判断数值是否相等

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
  • Double类的hashCode,这里doubleToLongBits函数中使用到了原生函数doubleToRawLongBits(),简单理解是通过bit来计算的,而位运算符和还有无符号右移保证返回为正数:
    public static int hashCode(double value) {
        long bits = doubleToLongBits(value);
        return (int)(bits ^ (bits >>> 32));
    }

equals方法

    public boolean equals(Object obj) {
        return (obj instanceof Double)
               && (doubleToLongBits(((Double)obj).value) ==
                      doubleToLongBits(value));
    }
  • Character类hashCode没有重写,继承Object类:
        public final int hashCode() {
            return super.hashCode();
        }
	    Character character1 = new Character('a');
        Character character2 = new Character('b');
        System.out.println("a:" + character1.hashCode());
        System.out.println("b:" + character2.hashCode());

但输出如下所示:这表明hashCode返回对应的ASCII码(这里有点疑问,明明是Object的hashCode,却返回了ASCII码,不太理解???)

和Object类一样,equals()也是直接判断内存地址是否相等

        public final boolean equals(Object obj) {
            return (this == obj);
        }
  • String类的hashCode则是通过函数h = 31 * h + val[i],这里的val是字符串的字符数组,相当于是根据每个字符的ASCII码计算的:
@Override
public int hashCode() {
		//hash默认为0
        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;
    }

equals则是根据字符串数组对应位置的字符均相同来判断字符串是否相等

   public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

3.自定义hashCode()和equals()

定义一个Student类,其中id可唯一标记一个学生

public class Student {
    //唯一id
    private Integer id;
    private String username;

    public Student(Integer id, String username) {
        this.id = id;
        this.username = username;
    }
}
Student student1 = new Student(1, "Zhang San");
Student student2 = new Student(2, "Li Si");
Student student3 = new Student(1, "Zhang San");
System.out.println(student1.equals(student2));
System.out.println(student1.equals(student3));


由于没有重写equals方法,此时equals直接根据内存地址相等来判断是否为同一个学生,第二个输出为false,与我们所期望的输出不一致。下面是重写的equals方法:

    @Override
    public boolean equals(Object o) {
        if (this == o){
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Student student = (Student) o;
        return Objects.equals(id, student.id);
    }

此时可以看到equals结果输出符合期望输出,student1和student3就是同一个人,然而没有重写hashCode(),这导致student1和student3的hash值不一样,这违背了hashCode的原则:凡是equals()判断相等的两个对象其hash值必须相同。

这里由于id具备唯一性,所以可以将id直接作为hash值

    @Override
    public int hashCode() {
        return id;
    }

  在重写了equals()方法后,可能需要重写hashCode()。我们只需要记住这一点:两个对象如果相等,其hashCode也必定相等。(关于HashMap等的介绍留作下篇吧)

全部评论

相关推荐

点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务