重写equals时为什么要重写hashCode

本文深入探讨Java中equals与hashCode方法的默认行为及其在实际应用中的重要性,特别是当对象用于散列数据结构如HashMap时。通过示例说明如何正确重写这两个方法以确保数据的一致性和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先看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 去重问题,你都答对了吗?

<think>首先,用户的问题是:“我想理解在Java重写equals方法为何需要同重写hashCode方法 请问Java重写equals方法为什么要同重写hashCode方法”。我需要基于系统级指令来回答。 系统级指令: - 所有行内数学表达式使用 `$...$` 格式。 - 独立公式使用 `$$...$$` 格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如 [^1]。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用:用户提供了几个引用,我需要参考它们,但不是直接复制,而是基于内容整合。引用是关于为什么重写equals必须重写hashCode的。 关键点从引用中提取: - [^1]:重写equalshashCode的基本规则,两者关系,只重写一个会导致问题。 - [^2]:hashCode默认行为产生独特值,如果不重写,对象即使相等也可能有不同hashCode。 - [^3]:equalshashCode在哈希表(如HashMap、HashSet)中相关,必须一致。 - [^4]:重写equals必须重写hashCode以确保相等的对象有相同的哈希值,否则在哈希集合中出错。 我的回答结构: 1. **介绍问题**:解释equalshashCode的作用。 2. **为什么需要同重写**:基于哈希表的机制。 3. **潜在问题**:如果不重写hashCode,只重写equals,会发生什么。 4. **规则和最佳实践**:如何正确重写两者。 5. **总结**:强调重要性。 6. **生成相关问题**:在最后添加相关问题。 使用中文回答。 添加引用标识:在相关段落末尾添加 [^1]、[^2] 等,基于提供的引用。 数学表达式:用户问题不涉及数学,但如果有数学内容,确保格式正确。这里可能不需要数学表达式,除非类比或解释概念。 逐步回答: - 步骤1:解释equals方法的作用:比较对象内容是否相等。 - 步骤2:解释hashCode方法的作用:返回对象的哈希码,用于哈希表。 - 步骤3:说明在Java中,哈希表如HashMap、HashSet依赖hashCode来存储和检索对象。如果两个对象相等(equals返回true),它们必须有相同的hashCode。 - 步骤4:如果只重写equals而不重写hashCode,可能导致相等的对象有不同hashCode,违反hashCode契约,导致哈希表行为错误。 - 步骤5:给出一个简单例子来演示问题。 - 步骤6:总结规则。 生成相关问题:在最后添加2-3个相关问题。 完整回答结构: - 开头:直接回答问题。 - 主体:分点解释。 - 结尾:总结和生成相关问题。</think>### Java重写equals方法为何需要同重写hashCode方法 在Java中,重写`equals`方法必须同重写`hashCode`方法,这是为了确保对象在哈希表(如`HashMap`、`HashSet`等)中的行为正确性。下面我将逐步解释原因,并基于Java规范和相关最佳实践进行说明。 #### 1. **equalshashCode方法的作用** - **equals方法**:用于比较两个对象的内容是否相等。默认情况下(从`Object`类继承),它比较对象的内存地址(即引用相等),但重写后可以基于对象的属性(如字段值)进行比较[^3]。 - **hashCode方法**:返回一个整数哈希码,用于在哈希表中快速定位对象。默认行为基于对象的内存地址生成唯一值[^2]。 - 关键关系:Java规范要求,如果两个对象通过`equals`方法比较为相等(`obj1.equals(obj2) == true`),那么它们的哈希码必须相同(`obj1.hashCode() == obj2.hashCode()`)。这称为“hashCode契约”[^1][^4]。 #### 2. **为什么必须同重写?** 如果不重写`hashCode`而只重写`equals`,会导致哈希表行为不一致,原因如下: - **哈希表依赖hashCode**:在`HashMap`或`HashSet`中,对象首先通过`hashCode`分配到“桶”(bucket)中,然后使用`equals`在桶内比较。如果两个对象相等但`hashCode`不同,它们会被分配到不同桶中,导致`equals`比较无法触发,从而无法正确识别相等对象[^3][^4]。 - **违反契约的风险**:只重写`equals`而不重写`hashCode`,相等的对象可能生成不同的哈希码(因为默认`hashCode`基于内存地址)。这破坏了Java的契约规则,可能引发逻辑错误,例如对象在集合中重复存储或无法查找[^1][^2]。 - **示例说明**:假设有一个`Person`类,重写了`equals`方法基于`name`字段比较: ```java class Person { String name; // 重写equals:基于name比较 @Override public boolean equals(Object obj) { if (obj instanceof Person) { return this.name.equals(((Person) obj).name); } return false; } // 未重写hashCode } ``` 如果创建两个`name`相同的`Person`对象(`person1`和`person2`),`equals`返回`true`,但默认`hashCode`不同。当存入`HashSet`: ```java Set<Person> set = new HashSet<>(); set.add(person1); set.add(person2); // 可能成功添加,因为hashCode不同导致分配到不同桶 ``` 结果:`set`大小可能为2,尽管对象内容相等。这违反了集合的唯一性要求[^4]。 #### 3. **如何正确重写两者** - **基本规则**: - 重写`equals`,确保比较对象的所有相关属性。 - 重写`hashCode`,必须使用与`equals`相同的属性计算哈希码,保证相等的对象产生相同的哈希值[^1]。 - 常用方法:在`hashCode`中使用`Objects.hash()`工具类,例如: ```java @Override public int hashCode() { return Objects.hash(name); // 基于name字段计算哈希码 } ``` - **最佳实践**:在IDE(如IntelliJ IDEA)中生成`equals`和`hashCode`方法,确保一致性。这避免了手动错误,并符合Java规范[^3][^4]。 #### 4. **总结** 重写`equals`必须重写`hashCode`,以维护哈希表的正确性和Java契约。如果不这样做,可能导致集合类(如`HashMap`)出现不可预测的行为,例如对象丢失或重复存储。始终确保两者同步,基于相同的属性实现[^1][^3][^4]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值