hashCode方法与equals方法

工作中编写代码的时候涉及到了重写equals方法和hashCode方法,一直都是重写equals方法时要重写hashCode方法,但是一直不知道原理,现在将学习到知识记录下来。(文章引用的JDK源码均为1.8版本)

先来几个问题:

1. hashCode和equals的作用都是什么?

2. 为什么需要重写equals()方法?

3. 为什么重写equals方法时需要同时重写HashCode方法?

 

hashCode和equals的作用都是什么?

hashCode的作用:hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap,HashSet等,hashCode是用来在散列存储结构中确定对象的存储地址的。

equals的作用:equals是用于比较两个对象的是否相等的。

在Object类中,equals方法的源码如下:

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

通过以上代码可以看出,在Object的equals方法中是比较两个对象的地址是否相等,地址相等才认为是相等。

Java中的很多类都重写了这两个方法,例如String类,Integer Long Double等包装类,String类的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) {// while循环, 逐个字符比较是否相等
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

 

为什么需要重写equals()方法?

我们在定义类时,我们经常会希望两个不同对象的某些属性值相同时就认为他们相同,所以我们要重写equals()方法。举例如下

package com.jd.real.stock.web.http.impl;

import java.util.Objects;

public class People {

    /*身份证号*/
    String idCard;

    /*名字*/
    String name;

    public People() {}

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

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

        People people = (People) o;
        return Objects.equals(idCard, people.idCard);
    }

    @Override
    public int hashCode() {

        return Objects.hash(idCard);
    }

    public static void main(String[] args) {
        People oneInFamily = new People("21042319930101666x", "小明");
        People oneInCompany = new People("21042319930101666x", "黄小明");
        // 虽然在家里和在公司他们的名字不一样, 但是通过身份证Id可以识别他们就是同一个人
        System.out.println("Are they the same person?" + oneInCompany.equals(oneInFamily));
    }
}


----------------------------------------------
Are they the same person?true

Process finished with exit code 0

 

为什么重写equals方法时需要同时重写HashCode方法?

前面讲到了,hashCode是用来在散列存储结构中确定对象的存储地址的。既然谈到散列的存储结构,挑两个典型的HashSet和HashMap了解一下原理。

HashSet的add(Objcect o)方法源码如下:


public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

    public HashSet() {
        map = new HashMap<>();
    }

    public boolean add(E e) {
// HashSet的底层实现原理实际是使用的HashMap,利用HashMap结构的Key来实现集合的去重效果
        return map.put(e, PRESENT)==null;
    }

}

HashMap的put源码如下:

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }


    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);// 如果出现Hash冲突, 以链表的结构存储新的Value,当前Hash地址下的老Value不会被删除
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

通过HashSet的add源码以及hashMap的put源码可以看出来,HashSet底层是使用HahMap实现的。HashMap的Key当出现Hash冲突的时候会判断当前Key和之前位置的Key是不是euqals,如果是同一个Key那么新的value会直接覆盖旧的Value。如果不equals,那么会在同一个Hash桶中以链表的形式存储下来。

掌握了以上知识,下面来说明为什么重写equals需要同时重写HashCode。

场景1:假如重写了equals没有重写HashCode,那Object中原始的hashCode方法时比较两个对象的内存地址是否相等。对于上述例子中,小明和黄小明显然是同一个人,如果没有重写hashCode, 将小明和黄小明存储到hashSet中时,就会发现都存入成功了(因为两个对象的HashCode不一致),就违反了HashSet的不可重复性。


场景2:假如重写了HashCode,但是hashCode的返回值是固定值。则会出现equals不等,HashCode相同的情况。针对这种场景,当存入HashMap时,由于大家的HashCode相同,所以所有对象都会落入同一个哈希桶中,这样哈希表就完全当做链表了,就失去了快速查找的特性了。

 

总结:


1、重写equals时一定要重写HashCode
2、如果equals不相等,HashCode一定也不相等。(否则就会出现场景2的问题)
3、如果equals相等,HashCode一定也相等。(否则就会出现场景1的问题)

 

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值