重写equals时为什么要重写hashCode

首先看Object对象中是如何定义equals 和 hashcode:

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


/*@return  a hash code value for this object.*/
public native int hashCode();

可以看出,equals实际上是比较对象的内存地址,而hashCode是获取当前对象的hash值;

定义一个对象

public class Person {
    private String name;
    private int IDCard;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getIDCard() {
        return IDCard;
    }

    public void setIDCard(int IDCard) {
        this.IDCard = IDCard;
    }

    public Person(String name, int IDCard) {
        this.name = name;
        this.IDCard = IDCard;
    }
}

在现实中,如果两个人表示同一样人,那么他们的名字相同,身份证也一样,那么一定是一个人。
 

public static void main(String[] args) {
        /*测试对象不需要重写hashcode*/
        Person a = new Person("zhangsan" ,101);
        Person b = new Person("zhangsan" ,101);


        System.out.println(a.equals(b));  //false
}

此时可以看出 结果是 false, 因为上面基类中真实比较的其实是内存地址,与对象内容无关;如果要与对象产生关系,那么此时就需要重写 equals方法。

@Override
    public boolean equals(Object obj) {

        Person p = (Person)obj;

        if(this.name == p.name && this.IDCard == p.IDCard){
            return true;
        }
        return false;
    }

此时再拿测试上面的main方法会发现,结果为true。

-------

那么什么时候需要重写hashCode 呢?

下面后一种情况,将人作为对象,年龄作为映射值:

public static void main(String[] args) {
        /*测试对象不需要重写hashcode*/
        Person a = new Person("zhangsan" ,101);
        Person b = new Person("zhangsan" ,101);


        System.out.println(a.equals(b));

        System.out.println(a.hashCode()+"-"+b.hashCode());


        /*测试对象需要重写hashcode*/

        Map m = new HashMap<Person,Integer>();
        m.put(a,10);

        m.put(b,20);

        System.out.println(m);

    }

输出:{com.string.Person@2f92e0f4=10, com.string.Person@28a418fc=20}

会发现a、b明明是同一个人,map 应该存放的只有一个年龄才对,本来应该后一个覆盖前一个,为什么会打印出两个结果?

查看HashMap源码:

// 将“key-value”添加到HashMap中 
    public V put(K key, V value) { 
        // 若“key为null”,则将该键值对添加到table[0]中。 
        if (key == null) 
            return putForNullKey(value); 
        // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。 
        int hash = hash(key.hashCode()); 
        int i = indexFor(hash, table.length); 
        for (Entry<K,V> e = table[i]; e != null; e = e.next) { 
            Object k; 
            // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出! 
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
                V oldValue = e.value; 
                e.value = value; 
                e.recordAccess(this); 
                return oldValue; 
            } 
        } 
 
        // 若“该key”对应的键值对不存在,则将“key-value”添加到table中 
        modCount++;
        //将key-value添加到table[i]处
        addEntry(hash, key, value, i); 
        return null; 
    }

这里的存的对象key其实是key的hashCode值,两个对象a、b地址是不一样的,自然会被认为是两个key值。

此时我们重写hashCode, 让hashCode值仅与name、IDCard有关,如下:

@Override
    public int hashCode() {

        int prime = 31;
        int result = 1;
        result = prime * result + name == null ? 0 : name.hashCode();
        result = prime * result + IDCard;

        return result;
    }

测试上面main,输出结果:

{com.string.Person@15eb48fe=20}

只有后一种了,前面的结果被覆盖了!

所有要注意这里:要考虑到类似HashMap、HashTable、HashSet的这种散列的数据类型的运用。

补充:

哈希算法有一个协定:在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行hashcode比较时所用的信息没有被修改

那么为什么要以上面那种方式重写hashCode呢?

String源码中对hashCode的重写给了我们启示:

/**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    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;
    }

31可以拆解为2<<4  -1, 虚拟机做了相关优化,本身计算机乘除移位效率很高,这可能是用31的原因。

ref:

重写equals方法的时候为什么需要重写hashcode - 简书

面试中常问的 List 去重问题,你都答对了吗?

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值