Java中的Object类是所有对象的公共父类。其中有两个方法。

  • equals
  • hashcode

所以每个对象都可以调用这两个方法。我们先来看看equals方法。

一、equals方法

equals方法通常是用来比较两个对象是否相等。这里我们要来跟==作一下区别。
==在比较基本数据类型时是值比较。在比较引用数据类型的时候是比较对象的内存地址是否一样。

而在Object类的equals方法中,对象之间是比较的内存地址是否相同。源代码如下:

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

可以看到,object类中的equals比较的也就是对象在堆中的内存地址。
这里我们测试一下。

java obj hashcode会变_hashcode

如果两个对象中的值相等,我们就判断这两个对象相等,该怎么办呢?很简单,重写父类的equals方法。

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    A a = (A) o;
    return bName.equals(a.bName);
}

这是idea自动帮我们生成的equals方法。它不光帮我们判断了两个对象的内存地址,
还帮我们判断了对象是否为null、对象的Class类对象是否一致(从jvm层面来看,确定一个类的一致性,需要判断这个类的本身以及加载这个类的类加载器)还有对象中bName属性是否一致。
重写了equals方法后,我们再去判断两个对象是否相等。

java obj hashcode会变_java_02

这次两个对象就是相等的了。

我们总结一下,对于==比较来说,基础类型是比较的值是否相同,对象比较的是内存地址是否相同。对于equals方法来说,如果不重写父类的equals方法,
那么默认就是使用Object类中的equals方法。如果自己重写了equals方法,那么就按照自己重写的equals方法来判断。

二、hashcode方法

hashCode方法也是Object中的方法。它的作用是获取这个对象的哈希码,这个哈希码的作用是确定该对象在哈希表(Hashmap,HashSet,HashTable)中的索引位置。虽然每个类都有hashcode方法,但是只有当创建某个“类的散列表”时,该类的hashCode方法才有用,作用是确定该类在散列表中的位置,其他情况下,类的hashCode没有作用。

散列表的本质是通过数组实现的。当我们要获取散列表中的某个“值”时,实际上是要获取数组中的某个位置的元素。而数组的位置,就是通过“键”来获取的;更进一步说,数组的位置,是通过“键”对应的散列码计算得到的。

我们可以来看看HashMap中的源码。

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

这是HashMap中的put方法和hash方法。我们可以看到当我们调用HashMap中的put方法时,会对key值做hash计算,这个hash方法中就调用了我们今天的主角hashCode方法。计算出hash值后,再调用putVal方法去存键值对。
当我们把对象加入到HashMap中,HashMap会先计算对象的hashcode值来判断对象加入的位置,同时也会与已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashMap会假设对象没有重复出现。但是如果发现有相同的hashcode值的对象,这时会调用equals方法来检查hashcode相等的对象是否真的相同。如果不同的话就散列到其他位置,相同的话就覆盖原来的对象。

三、equals和hashCode方法

面试的时候经常会被问到一道面试题:重写了equals方法后为什么要重写hashCode方法?
首先要有一个概念,那就是:如果两个对象相等,那么它们通过equals方法比较一定相同,并且它们的hashcode值也是相同的;反过来,如果两个对象的hashcode值相同,不能说这两个对象就是相同的,还要再通过equals方法比较一下。那为什么两个对象的hash值相同不能确定这两个对象相同呢?
因为hash计算存在一个哈希碰撞的问题,越好的哈希算法哈希碰撞的概率就越低,但是还是会存在哈希碰撞的情况。所以两个对象的哈希值相同,也有可能是这两个对象的hash计算值一样,但两个对象本身是不一样的。所以在散列表的对象比对中,
先通过hashcode值来比较两个对象,如果两个对象的hash相同,再去用equals方法比较。
让我们来看一看,只重写equals方法是什么情况。
对象A类重写了equals方法:

public class A {
    public String bName;

    public A(String bName) {
        this.bName = bName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        A a = (A) o;
        return bName.equals(a.bName);
    }
}

我们都知道,HashMap是不允许存放重复key的。让我们看看测试类:

java obj hashcode会变_散列表_03

我们可以看到,虽然两个对象通过equals比较之后返回的是true,也就说明两个对象实际上是相等的,但是当我们遍历了HashMap之后我们发现对象a和b都在map里面,并没有被覆盖掉。
让我们重写hashcode方法。
对象A类重写了equals和hashcode方法:

public class A {
    public String bName;

    public A(String bName) {
        this.bName = bName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        A a = (A) o;
        return bName.equals(a.bName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(bName);
    }
}

测试类:

java obj hashcode会变_java obj hashcode会变_04

这里我们可以看到。重写了hashcode方法之后,map中只能存在一个对象了。因为在map.put(b)时,知道了map中已经存在相同的对象了,所以用新值覆盖掉旧值。