面试官问我为什么重写了equals方法就要重写hashcode方法,我反手说你觉得呢?

理解equals()方法和hashcode()方法

  1. equals()方法和hashcode()方法都是Java中万物之源Object类中的方法;
  2. equals()方法的作用是比较两个对象是否相同,Object类中equals()方法是通过比较两个对象的引用地址来判断是否是同一个对象,当然也可以重写该方法来实现自定义的判断规则(比如String就重写了该方法,比较的是两个对象的内容是否相等,而不是比较地址值了);
  3. hashcode()方法是获取对象的哈希吗值的方法,值得一提的是:对象的物理地址跟这个hashcode地址是不一样的,hashcode地址是对象在hash表中的位置,物理地址是对象存放在内存中的地址。

为什么equals和hashcode需要同时覆盖?

先准备一下后续会使用到的案例:

package com.jt.pojo;
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("wyf", 30);
        Person p2 = new Person("wyf", 30);
        System.out.println(p1.hashCode());//1829164700
        System.out.println(p1.hashCode());//2018699554
        System.out.println(p1.equals(p2));//false
    }
}
class Person{
    private String name;
    private Integer age;
    public Person(String name, Integer age){
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在这里定义了一个Person类,里面有name和age两个属性,重写了toString()方法,避免打印出对象的地址。然后再main方法里new了两个对象,对象的属性值都一样,然后通过equals方法来判断这两个对象是否相等。毋庸置疑,最后结果肯定是false,这也是我们所期待的答案,毕竟使用了new关键字,肯定是会开辟两个空间来存放各自对象的,所以两个对象的地址必定不一样,那比较的结果肯定是false了。

我先说一下为什么要同时覆盖equals和hashcode这里面的逻辑,可能对于初学者有点绕,但后面会结合案例具体解释:

众所周知,如果只重写了equals()方法,则比较的是内容,上面案例两个对象比较的结果就会是true(前提是重写了equals方法)。但是我们只重写了equals方法,又没有重写hashcode方法,则我们通过new出来的两个对象的hashcode值是不一定相等的,那如果两个对象的hashcode值都不相等了,那这两个对象肯定是不相同的。但是又因为如果两个对象通过equals比较的结果是true,则两个对象的hashcode值是肯定相同的,这不就就产生了矛盾啊,所以说,重写equals就必须重写hashcode。

储备知识:

  • 如果两个对象通过equals比较的结果是true,则他们的hashcode值必定一样。
  • 如果两个对象的hashcode值不相等,则他们的equals比较的结果必定是false。

对象只重写equals之后的比较

我们对equals方法进行覆盖以后再来比较:

package com.jt.pojo;
import java.util.Objects;
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("wyf", 30);
        Person p2 = new Person("wyf", 30);
        System.out.println(p1.hashCode());//1829164700
        System.out.println(p2.hashCode());//2018699554
        System.out.println(p1.equals(p2));//true
    }
}
class Person{
    private String name;
    private Integer age;
    public Person(String name, Integer age){
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name) &&
                Objects.equals(age, person.age);
    }
    
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在这里我们只是覆盖了equals方法,还没有覆盖hashcode方法,这样比较的结果就由原来的false变成了true。这是因为如果没有覆盖equals方法,我们比较的是对象的地址,那比较的结果就为false。那如果我们覆盖了equals方法,我们比较的是对象的内容,他们的内容都是“Person{name='wyf', age=30}”,所以比较的结果就是true。

这就让人容易想到:那两个对象的比较结果都为true了,那他们肯定就是同一个对象了吧。额~~,虽然逻辑是没错,但是咱们仔细分析一下就知道他们不是同一个对象:因为你要是同一个对象的话,你俩对象的地址得是一样的啊,但是很显然,咱们这两个对象是通过new关键字new出来的,所以肯定会产生两个不一样的空间(也即不一样的地址),所以你俩就不是同一个对象(你俩没有血缘关系) 

所以说,为了避免产生上述那样的误解,我们就需要同时覆盖equals方法和hashcode方法。使得对象通过equals比较的结果为true的时候,他们的hashcode值是一样的,这样才能真正保证这两个对象相等。

对象只重写hashcode之后的比较

上面是只重写了equals的案例,下面看一下只重写了hashcode方法之后两个对象的比较:

package com.jt.pojo;

import java.util.Objects;

/**
 * @Author 作者:曾龙
 * @Project 项目:cgb2105
 * @Time 时间:2021/8/4 19:10
 */
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("wyf", 30);
        Person p2 = new Person("wyf", 30);
        System.out.println(p1.hashCode());//3665563
        System.out.println(p2.hashCode());//3665563
        System.out.println(p1.equals(p2));//false
    }
}
class Person{
    private String name;
    private Integer age;
    public Person(String name, Integer age){
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

可以看到结果,重写了hashcode之后,两个对象的hashcode值一样了,但是他们的equals结果还是false,这是因为:没有重写equals的时候,使用的是万物之源Object的equals方法,此时比较的是两个对象的地址值(注意是地址值,而不是hashcode值),我们上面也说过,使用new关键字new出来的对象各自的地址肯定是不同的,所以在这里比较的结果是false.

对象重写equals和hashcode之后的比较

现在我们同时覆盖equals方法和hashcode方法:

package com.jt.pojo;
import java.util.Objects;
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("wyf", 30);
        Person p2 = new Person("wyf", 30);
        System.out.println(p1.hashCode());//3665563
        System.out.println(p2.hashCode());//3665563
        System.out.println(p1.equals(p2));//true
    }
}
class Person{
    private String name;
    private Integer age;
    public Person(String name, Integer age){
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name) &&
                Objects.equals(age, person.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

可以看到我们重写了 equals方法和hashcode方法,通过equals方法比较的结果是true,而且他们的hashcode值也是一样的,这样才能说这两个对象相等。

有人可能会说:你这不对啊,你开头那个案例还说这两个对象都是new出来的,地址肯定不一样,结果是false才是我们所期望的,这怎么到最后结果又变成了true,这不就和我们期望的矛盾了吗。额~~,我刚开始也还没有反应过来,这是因为咱们忘了最初的问题了:为什么重写了equals就要重写hashcode方法?这才是我们的问题,咱们注意看,问题是这样的逻辑,咱们得先重写了equals,才有这样的问题...我们开头那个案例还没有先重写equals,所以比较的结果是false是我们期望的。但是如果咱们已经重写了equals方法,此时再比较的话,比较的就是内容了,结果为true,但是他们的hashcode值又不一样,所以他们并不是同一个对象...感觉又扯回去了。

简而言之吧:我们之所以同时覆盖equals方法和hashcode方法,是为了使得对象通过equals比较的结果为true的时候,他们的hashcode值是一样的,这样才能真正保证这两个对象相等。

值得注意的是:我们平常所说的两个对象的比较,一般都是重写了equals方法和hashcode方法,整体分为两个步骤,先是比较两个对象的hashcode值,然后再通过equals比较。但是如果两个对象的hashcode值都不相同的话,也就没有必要进行equals的比较了;如果两个对象的hashcode值相同的话,然后再判断两个对象equals的比较结果(比较的是内容),如果为true,则认为两个对象相同,如果为false,则认为两个对象不相同。

总结

  1. hashCode是为了提高在散列结构存储中查找的效率,在线性表中没有作用。
  2. equals和hashCode需要同时覆盖。
  3. 若两个对象equals返回true,则hashCode有必要也返回相同的int数。
  4. 若两个对象equals返回false,则hashCode不一定返回不同的int数,但为不相等的对象生成不同hashCode值可以提高哈希表的性能。
  5. 若两个对象hashCode返回相同int数,则equals不一定返回true
  6. 若两个对象hashCode返回不同int数,则equals一定返回false。
  7. 同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值