为什么要重写hashCode和equals方法【深入分析版】

 在回答这个问题前,我们先来看看Object类中的这两个方法:

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

其中hashCode调用的是本地方法,如果子类补充下默认调用的是本地方法。Java平台有个用户和本地C代码进行互操作的API,称为Java Native Interface (Java本地接口)。

equals方法比较的是内存地址是否相等,因此一般情况下也无法满足两个对象值的比较。

  1. 重写equals方法是为了比较两个不同对象的值是否相等
  2. 重写hashCode是为了让同一个Class对象的两个具有相同值的对象的Hash值相等。
  3. 同时重写hashCode()与equals()是为了满足HashSet、HashMap等此类集合的相同对象的不重复存储。

 

下面做个试验,用同一个Student类创建两个实例对象,然后比较各种情况下,这两个对象是否相等。

1、先将Student中的hashCode()方法注释掉 

package com.school.eution.accommodation;

import java.util.Objects;

public class Student {
    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student() {
        super();
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }

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

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

进行如下测试: 

package com.school.eution.accommodation;
 
import java.util.HashMap;
import java.util.Map;
 
public class Test {
    public static void main(String[] args) {
        Student s1 = new Student(1,"June");
        Student s2 = new Student(1,"June");

        Map testMap = new HashMap();
        testMap.put(s1, "美国学生");
        System.out.println(s1.hashCode());
        System.out.println(testMap.get(s1));

        System.out.println("------------------");
        System.out.println(s2.hashCode());
        System.out.println(testMap.get(s2));

        System.out.println("------------------");
        System.out.println(s1.equals(s2));
    }
}

 多次运行结果一致:

971848845
美国学生
------------------
1910163204
null
------------------
true

解释:如果不重写hashCode方法就会使用从Object继承来的本地hashCode()方法,所以将对象s1放入Map中后,通过s1对象能从Map中取出来字符串“美国学生”,但是s2和s1在值上是相等的,却不能使用s2对象从Map中也获取到同样的值。就是因为没有重写hashCode方法导致使用默认hasCode产生的Hash值不一致造成的。所以s2取值的时候按照s2的hash值“1910163204”就在Map中找不到。

但是如果不使用Map取值,直接通过equals比较是相等的(此时并没有重写hashCode)。

Map取值是先通过hash值定位,如果hash值存在于Map中,并且hash值一致就说明在Map的同一个链表中,继续使用equals方法比较值是否相等,如果hash值都不一致,就没必要往下比较了直接返回false。

下面将Student中重新hash的代码放开

package com.school.eution.accommodation;

import java.util.Objects;

public class Student {
    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student() {
        super();
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }

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

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

再次运行结果:

2321474
美国学生
------------------
2321474
美国学生
------------------
true

因为重新了hashCode,此时,s1与s2的age与name值又是相等的,所以使用对象s1、s2都能从Map中取得相同的值。即使s2事先并没有向Map中存放,也就是可以使用s2与s1Hash值一样,来用s2获取s1存放到Map中的值。就好比s1与s2拿了同一把钥匙。

通过equals方法比较s1与s2依然是相等的。

 

总结:为什么一定要同时重写hashCode()方法和equals()方法,是针对HashSet和HashMap等这类使用hash值存储的对象而言的。比如:在向HashSet存放Student时,如果没有重写hashCode,这时往HashSet中存放s1、s2实际上是能存放两份的,即使s1与s2的值完全一致。但如果用不到这些Hash的集合,只重写equals()方法也能满足像个对象值是否相等的比较。

 

 

你可能感兴趣的:(面试题总结,重写hashCode,重写equals,java,面试)