hashcode()与equal()

98 阅读4分钟

equal()方法

equal()方法定义在Object内,所有的对象都继承了这个方法。

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

在Obejct源码内,equals方法使用==号来比较对象,==号是通过地址值来判断对象是否相等。

重写

Obejct 对象的equals方法有太多局限性,对于我们的String类,我们不希望通过地址值来判断是否相等,而是通过字面量来判断,虽然对于String类的对象,如果他们的地址值相等,他们的字面量也肯定相等。

判断以下代码

public static void main(String[] args) {
    String a="tom";
    String b=new String("tom");
    System.out.println(a==b);
    //false
}

结果是false,因为通过new去构建字符串它并不会去查询常量池,而是在堆内存里新建一个对象来存放字符串对象,这个时候我们就需要使用String 类重写的equals方法来进行比较。

public static void main(String[] args) {
    String a="tom";
    String b=new String("tom");
    System.out.println(a.equals(b));
}
//true

我们来看看String类中equals源码

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String aString = (String)anObject;
        if (!COMPACT_STRINGS || this.coder == aString.coder) {
            return StringLatin1.equals(value, aString.value);
        }
    }
    return false;
}

可以看到重写方法并不是单纯比较字面量,首先比较两个对象地址,地址相同则两个对象和对象字面量肯定是相同的,地址不同再去比较字面量。

方法内部使用了强制转化 String aString = (String)anObject; 这使得该方法可以比较不同类型的对象。

在Java中有很多对象都重写了equals方法,比如Integer,Double这些包装类等。

hashcode()

hashcode()也是Object类中定义的方法,也被所有的类继承。

hashcode方法返回一个哈希值,该哈希值由哈希函数生成,哈希函数可以保证相同的对象有相同的哈希值,但不能保证不同的对象有不同的哈希值。

也就是说,如果两个对象相同,那么他们的哈希值肯定相同,两个对象的哈希值相同,但是对象可能不同。

我们把不同对象产生相同的哈希值称为哈希冲突,当样本足够大时哈希冲突很可能产生。

二者之间的关系

为什么重写了equals方法必须重写hashcode方法?

我们找到hashcode的定义

* Note that it is generally necessary to override the {@code hashCode}
 * method whenever this method is overridden, so as to maintain the
 * general contract for the {@code hashCode} method, which states
 * that equal objects must have equal hash codes.
 *
 * @param   obj   the reference object with which to compare.
 * @return  {@code true} if this object is the same as the obj
 *          argument; {@code false} otherwise.
 * @see     #hashCode()
 * @see     java.util.HashMap
 */
public boolean equals(Object obj) {
    return (this == obj);
}

大致意思就是如果重写了equals方法就必须要重写hashcode方法,以保证相同的对象有相同的哈希值,这个约定仅仅是口头约定,不执行也不会报错,不过会违反hashcode的定义。

不过我个人对此理解是为了实现对象对哈希表的支持。

两者的应用

hashmap是一个存储v键值的散列表,里面的元素都是不重复的,也就是说不存放相同的对象。它的底层实现是哈希表,而哈希表是利用对象的equals和hashcode方法来实现元素不重样的

哈希表是一个链表数组,在向哈希表存放元素时,会调用元素的hashcode方法,生成的哈希值又会映射到数组的下标,用来表示这个元素时存放在那个链表里面,我们使用hashSet来举例,往一个哈希表添加一个元素张三。

public static void main(String[] args) {
    Set<String> strings=new HashSet<>();
    String a="张三";
    strings.add(a);
}

image.png

张三的哈希值假设被映射到了数组的0下标,该内存为空,则直接存放即可。

我们再添加一个新元素李四

public static void main(String[] args) {
    Set<String> strings=new HashSet<>();
    String a="张三";
    String b="李四";
    strings.add(a);
    strings.add(b);
}

假设李四的哈希值为2,直接存放

image.png

再次存入一个元素,王五,王五的哈希值为0,与张三发生哈希冲突,jvm会调用王五的equals方法与张三比较,如果相同则直接覆盖,不相同则以张三为表头元素开启一个链表,王五存放在链表后面。

public static void main(String[] args) {
    Set<String> strings=new HashSet<>();
    String a="张三";
依此类推String b="李四";
    String c="王五";
    strings.add(a);
    strings.add(b);
    strings.add(c);
}

image.png

假设我们将张三再次存入哈希表,再发生哈希冲突后,jvm将张三和链表上所有元素比较,如果发现重复的则直接覆盖旧值,这就是哈希表存入元素不重样的原理。

哈希表的元素的查找也是一样的,先判断元素在那个链表上,再和链表上的元素比较,如果相同则查找成功。

由于哈希表独特的存储方式,所以他的查询和插入速度都非常快,相当于结合了链表和数组的优点。