为什么重写equals还要重写hashcode?

equals()和hashcode()是java.lang.Object中提供的用以对象比较的两个重要方法,下面是其定义及默认实现:

  • public boolean equals(Object obj) { return (this == obj); }:用以判断变量参数与当前实例是否相等,JDK默认实现是基于对象内存地址是否相同,如果两个对象内存地址相同,则表示两个对象相同。
  • public native int hashCode();: 默认情况下,该方法返回一个随机整数,对于每个对象来说该数字唯一,但该数字并非恒定,可能随着程序的执行发生变化。
    equals() 与 hashCode() 使用契约
    If two objects are equal according to the equals(Object) method, then calling the hashcode() method on each of the two objects must produce the same integer result

上面的话翻译成程序语言即是:

if obj1.equal(obj2) then:
obj1.hashCode() == obj2.hashCode()

在实际应用当中,JDK提供的默认实现可能无法满足实际业务场景,这时,我们就需要根据业务场景来重载hashCode和equals方法,但需谨记:当我们重载一个对象的equals方法,就必须重载他的hashCode方法,如果我们仅仅重载equals但没有重载hashcode,实际应用可能会带来潜在问题,接下我们示例说明:

示例说明
定义Student类

public class Student {
    private int id;
    private String name;
    public Student(int id, String name) {
        this.name = name;
        this.id = id;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

测试主类HashcodeEquals,测试两个Student类实例是否相同

public class HashcodeEquals {
    public static void main(String[] args) {
        Student alex1 = new Student(1, "Alex");
        Student alex2 = new Student(1, "Alex");
        System.out.println("alex1 hashcode = " + alex1.hashCode());
        System.out.println("alex2 hashcode = " + alex2.hashCode());
        System.out.println("Checking equality between alex1 and alex2 = " + alex1.equals(alex2));
    }
}

输出如下:

alex1 hashcode = 1852704110
alex2 hashcode = 2032578917
Checking equality between alex1 and alex2 = false

上面的输出也很好理解:尽管两个实例属性完全相同,但根据JDK中equals的默认实现规则,他们的内存地址不同,因此,在equals的默认实现中,它们被认为是不相等的,这同样适用于hashCode —— JDK为每个实例生成一个随机的惟一哈希值

重载equals()方法
实际业务场景中,如果两个student实例ID相同,那么我们即可认为实例相同,因此,我们重载equals方法,重载规则如下:内存地址一致或者实例ID相同,代码如下:

@Override
public boolean equals(Object obj) {
    if (obj == null) return false;
    if (!(obj instanceof Student))
        return false;
    if (obj == this)
        return true;
    return this.getId() == ((Student) obj).getId();
}

重新执行判断语句,结果如下

alex1 hashcode = 2032578917
alex2 hashcode = 1531485190
Checking equality between alex1 and alex2 = true

ArrayList中使用equals()方法
equals方法另一个常用业务是从一组实例中查找指定实例是否存在:

public class HashcodeEquals {
    public static void main(String[] args) {
        Student alex = new Student(1, "Alex");
        List < Student > studentsLst = new ArrayList < Student > ();
        studentsLst.add(alex);
        System.out.println("Arraylist size = " + studentsLst.size());
        System.out.println("Arraylist contains Alex = " + studentsLst.contains(new Student(1, "Alex")));
    }
}

执行结果如下:

Arraylist size = 1
Arraylist contains Alex = true

重载hashCode()方法
通过上面的实例我们可以看到,通过重载equals方法,我们得到了我们期待的结果,即使是两个实例对象的哈希值并不相同,那么问题来了?重载hashCode方法的目的何在?

HashSet中使用equals方法
我们用一个例子来说明这个问题:

public class HashcodeEquals {
    public static void main(String[] args) {
        Student alex1 = new Student(1, "Alex");
        Student alex2 = new Student(1, "Alex");
        HashSet < Student > students = new HashSet < Student > ();
        students.add(alex1);
        students.add(alex2);
        System.out.println("HashSet size = " + students.size());
        System.out.println("HashSet contains Alex = " + students.contains(new Student(1, "Alex")));
    }
}

运行上面的代码,我们得到如下输出:

HashSet size = 2
HashSet contains Alex = false

上述例子中,我们应该已经看出了问题,尽管我们已经重载了equals方法,理论上 alex1与 alex1应该是同一个对象,而HashSet是无法存储重复对象,但为什么JVM会认为他们是不同的对象。

这涉及到了HashSet的内部存储结构,在JDK中,HashSet将其内部元素存储在内存桶中。每个内存桶都关联一个特定的哈希值。在调用student.add(alex1)时,Java将alex1存储在一个内存桶中,并将其与alex1.hashCode()的值关联。后续,一旦插入相同哈希值的对象,那么插入对象将替换原有对象alex1。但是,由于alex2与alex1哈希值不同,因此被JVM视为完全不同的对象,将其存储在另外一个单独的内存桶中。

这就是重载hashCode的重要性,因此,我们按照业务场景重载hashCode方法,确保拥有相同ID的Student实例存储在同一个内存桶中,代码如下:

@Override
public int hashCode() {
    return id;
}

之后,我们重写运行测试代码,执行结果如下:

HashSet size = 1
HashSet contains Alex = true

这就是hashCode的魅力所在,这两个实例被认为是一个对象并存储在同一个内存桶中,后续通过contains方法查找对象时,只要哈希值相同,对象即可被查到,这样适用于使用哈希机制存储对象的数据结构,如:HashMap, HashTable

结论 如果两个对象相同,则他们的哈希值(hashcode)一定相同

如果两个对象的哈希值相同(hashcode)相同,并不意味着他们是相同的。

对于使用Hash散列方式存储对象的数据结构:HashSet、HashMap、HashTable等,仅仅重载equals方法可能会导致实际业务逻辑失败

在比较两个对象时,仅重载hashCode方法并不能强制Java忽略内存地址。

https://dzone.com/articles/working-with-hashcode-and-equals-in-java

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值