本文内容:浅析Java里的HashCode()方法和Equals()方法。

一、Object类里的equalshashCode

Object是Java中所有类的“老祖宗”,所有对象都自带这两个方法,它们的默认行为是这样的:

1. equals方法:默认比较“是不是同一个对象”

equals的默认逻辑很简单:判断两个对象是不是内存中同一个“实体”
比如,你new了两个Person对象:

Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);

默认情况下,p1.equals(p2)会返回false——因为它们是两个不同的对象(内存地址不同),就像两片看起来得一模一样的树叶,虽然“看起来一样”,但本质是两片不同的叶子。

Object类的equals()方法:通过==比较内存地址,只有内存地址相同的对象才返回true。

2. hashCode方法:默认返回“对象的内存地址码”

hashCode会返回一个int值,你可以理解为对象的“内存身份”——默认情况下,这个值是根据对象的内存地址计算出来的。
因此,上面的p1p2,默认的hashCode也一定不同(因为内存地址不同)。

Object类的hashCode()方法:返回对象内存地址的整数表示。

二、为什么要重写?

Object类默认的逻辑只关心“是不是同一个对象”,但实际开发中,我们常需要判断“两个对象的内容是否相同”。

比如,对于Person类,我们可能认为:只要nameage完全相同,就应该算“同一个人”(逻辑上相等)。

Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);

p1和p2的内存地址是不相同的。
如果用Object默认的hashCode和equals方法,上面代码里的p1和p2就完全不等,也就“不是同一个人”,但实际上这确实是同一个人。
这时候,默认的equalshashCode就不够用了,必须重写。

三、重写规则

重写时有一个铁律

如果两个对象通过equals比较,返回true(逻辑相等),那么它们的hashCode必须返回相同的值;
反过来,hashCode相同的两个对象,equals不一定返回true(“哈希冲突”或“哈希碰撞”)。

哈希冲突或哈希碰撞:不同数据经哈希函数处理后,意外得到相同哈希值的现象。
为什么有这个规则?
因为HashMapHashSet等集合会先用hashCode快速定位对象(类似字典的“目录”),再用equals精确比较。
如果违反规则,可能导致集合判断出错(比如把“逻辑相等”的对象当成不同对象存储)。
还是这个代码。

Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);

如果要在哈希集合里存储不重复的元素,那么首先会通过p1和p2的hash值(hashCode()方法返回的值)找到一个存储的索引(简单理解为数组的索引),如果hash值不相等(未重写hashCode()方法),那么p1和p2就会存储在不同的地方,如:
在这里插入图片描述
这时候,p1和p2明明代表的就是同一个人——即:p1和p2都表示张三这个人。
那这样以来,就表示这个数据结构存储了两个张三,很明显是不行的,违背了“不重复”的原则。
所以要重写hashCode()方法。
那如果重写hashCode()方法以后呢?
此时p1和p2的hash值相等了,通过hash值计算得来的存储索引值相同,就像这样:
在这里插入图片描述
此时p2计算得来的索引是0,刚好就是p1存储的位置,可是这个位置已经存储有元素了(也就是p1)。
此时,equals()方法就派上用场了。
如果使用Object类的equals()方法(未重写equals()方法),通过equals()方法比较p1和p2的值,如果相等,返回true,不相等则返回false。
很明显,Object类默认的equals()方法会让p1和p2的比较结果为false(因为p1和p2这两个对象的内存地址不同)。
由于要存储的是“不重复”的元素,既然equals比较的结果是false,那就默认p1和p2是两个不同的东西,此时就会把p2存进去,如:
在这里插入图片描述
这时,索引0的元素就是一个元素为p1和p2的链表。
很明显,它不满足元素“不重复”的要求,因为p1和p2都指的是张三这个人。
但如果重写了equals()方法,通过equals比较得p1和p2是相等的,返回值为true。
那么就不会把p2存进去,因为一开始就说了,要存储“不重复”的数据。
既然得到的结果是p1和p2相等,就不存p2,只存了p1。
此时就满足一开始的要求。
很明显,重写这两个方法有多重要。

四、如何重写equals

核心目标:比较对象的“关键属性”是否相同(比如Personnameage)。

简单讲就是比较对象的关键属性(如:人的姓名、性别等数据)是否相同,相同返回true,不同就返回false。——而不是使用Object类默认的equals():比较对象内存地址值,相同为true,不同为false。

步骤通常是:

  1. 先判断“是不是同一个对象”(this == obj),是则直接返回true

比如:p1.equals(p1)里面的第一个p1和第二个p1就是同一个对象

  1. 再判断参数是否为null,或是否和当前对象“同类型”(避免比较不同类的对象),不是则返回false

p1.equals(null)或p1.equals(dog),很明显张三这个人明显不是虚无(null)或狗(dog)。

  1. 把参数强转为当前类类型,然后逐个比较“关键属性”(引用类型用equals,基本类型用==)。

p1.equals(p2),通过比较p1和p2的姓名、年龄等属性,如果相等,则说明p1和p2在逻辑上是同一个“东西”。比如:p1的姓名、年龄、身高、体重、爱好等数据都和p2的一样,就默认p1和p2是同一个人。

五、如何重写hashCode

核心目标:基于“关键属性”计算哈希值,确保equalstrue的对象哈希值一定相同。

简单讲就是通过对象的关键属性(如:人的姓名、性别等数据),计算出一个整数类型的hash值,然后把这个值返回。——而不是使用Object类默认的hashCode():通过对象内存地址值计算hash值并返回。

最简单的方式是用Java提供的Objects.hash()方法,它会自动组合多个属性的哈希值,还能处理nullnull的哈希值为0)。

六、实例:重写Person类的equalshashCode

假设Personname(String)和age(int)两个关键属性,重写如下:

import java.util.Objects;

class Person {
    private String name;
    private int age;

    // 构造器、getter等省略

    @Override
    public boolean equals(Object o) {
        // 1. 同一个对象,直接返回true
        if (this == o) return true;
        // 2. 非同一类型或null,返回false
        if (o == null || getClass() != o.getClass()) return false;
        // 3. 强转后比较关键属性
        Person person = (Person) o;
        // 基本类型用==,引用类型用Objects.equals(避免空指针)
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        // 基于关键属性计算哈希值
        return Objects.hash(name, age);
    }
}

这样重写后:

  • 两个nameage相同的Person对象,equals返回truehashCode也相同;
  • 符合“铁律”,在HashMap等集合中能正常工作。

总结

  • equals:默认比“内存地址”,重写后比较“关键属性是否相同”(逻辑相等);
  • hashCode:默认是“内存地址码”,重写后基于“关键属性”计算,确保逻辑相等的对象哈希值相同;
  • 重写时必须成对重写,优先用Objects工具类简化代码(避免空指针问题)。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐