3

面试官让你说说==和equals()的区别,重写equals必须重写hashcode方法吗

  • 本身特质来说

    • ==:操作符
    • equals():方法
  • 适用对象

    • ==:主要用于基本类型之间的比较(char、Boolean、byte、short、int、long、float、dobule),也可以用于比较对象
    • equals():对象之间的比较(基本类型的包装器类型,string,自己定义的对象等)
  • 比较对象时的区别

    • ==:比较两个对象是否指向同一个对象,也就是说他们指向的对象的首地址是否相同
    • equals():可以通过重写equals方法从而比较对象的内容是否相同,如果不重写那么和==符号没有区别,都是比较的对象的引用是否指向同一个对象

对于一个对象student来说,如果我们不重写它的equals方法,那么和==符号一样比较的是对象的引用而不是内容

public class Student {
    private int id;
    private String name;
    private String password;


    public Student(int id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

}
public class Test2 {
    public static void main(String[] args){

        Student s1 = new Student(1, "小王", "123456");
        Student s2 = new Student(1, "小王", "123456");
        System.out.println(s1 == s2);//false

        System.out.println(s1.equals(s2));//false
    }
}

上面两个对象s1和s2不相等,因为他们指向的是两个不同的对象,所以引用不同,但是我们的目的是要达到如果id,name,password都相同,那么就是同一个对象,所以需要重写equals()方法

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Student student = (Student) o;

    if (id != student.id) return false;
    if (name != null ? !name.equals(student.name) : student.name != null) return false;
    return password != null ? password.equals(student.password) : student.password == null;
}

这个时候我们再运行

public class Test2 {
    public static void main(String[] args){

        Student s1 = new Student(1, "小王", "123456");
        Student s2 = new Student(1, "小王", "123456");
        System.out.println(s1 == s2);//false
        System.out.println(s1.equals(s2));//true
    }
}

对于string类型来说,它的的equals()方法是对object方法的equals()进行了重写,从而比较的字符串序列是

否相同如下:

String s1 = new String("abc");//s1存在于堆内存中
String s2 = new String("abc");//s2也存在于堆内存中
System.out.println(s1 == s2);//false s1和s2指向的对象的首地址不一样,不是同一个对象
System.out.println(s1.equals(s2));//true  s1和s2指向的对象的内容相同

ps:

String s3 = "abc";
String s4 = "abc";
System.out.println(s3 == s4);//true 
System.out.println(s3.equals(s4));//true

接下来我们讨论一下重写equals()方法的同时必须要重写hashcode()方法吗

​ 首先我们重写equals()的目的就是为了让内容相同的对象让它们相同,而不是单单只比较对象的引用(对象的首地址),也就是尽管这两个对象的引用地址不同,但是我们调用equals方法的时候仍然返回true

​ 那么这个时候我们为什么又要重写hashcode方法呢,hashcode()返回的是对象的地址,是一个散列值,那么如果我们通过equals()方法得到这两个对象相同,尽管他们在堆中的内存地址不一样,但是我们希望他们的哈希值是一样的,这样如果存入map的话,就能定位到相同的索引

​ 同时Java标准中对hashcode有如下的规定:

  • 在java应用程序执行期间,如果在equals方法比较中所用的信息没有被修改,那么在同一个对象上多次调用hashCode方法时必须一致地返回相同的整数。如果多次执行同一个应用时,不要求该整数必须相同。
  • 如果两个对象通过调用equals方法是相等的,那么这两个对象调用hashCode方法必须返回相同的整数。
  • 如果两个对象通过调用equals方法是不相等的,不要求这两个对象调用hashCode方法必须返回不同的整数。

如果我们不重写student的hashcode()方法,那么就会默认调用object的hashcode()方法:

public class Test2 {
    public static void main(String[] args){

        Student s1 = new Student(1, "小王", "123456");
        Student s2 = new Student(1, "小王", "123456");
        System.out.println(s1 == s2);//false
        System.out.println(s1.equals(s2));//true
        System.out.println(s1.hashCode());//356573597
        System.out.println(s2.hashCode());//1735600054
    }
}

我们可以看到以上的运行结果违背了hashcode的规定:如果equals()返回true,那么hashcode方法必须返回相同的整数

所以我们需要对student对象的hashcode方法进行重写

@Override
public int hashCode() {
    int result = id;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    result = 31 * result + (password != null ? password.hashCode() : 0);
    return result;
}

通过重写hashcode()让其与对象的属性关联起来,那么就能够达到equals()为true,那么hashcode的值也相等。

现在我们已经知道了重写equals()方法的同时需要重写对象的hashcode()方法,让其满足hashcode的标准条件。

但是好奇的同学可能会想到:为什么hashcode需要这样定义标准呢,这样做到底有什么好处呢,除了让equals()方法和hashcode()方法的返回值具有一致性。

这时我们就需要提到map类了,我们知道hashmap的结构是一个数组加链表组成的,我们通过key的

​ hashcode % hashmap的capacity 定位到具体数组的索引,然后将该(key,value)放入该索引对应的链表里面,这里之所以为链表就是为了解决hash冲突,即hashcode % capacity 相同的值有很多,需要用一个链表存储起来,如果想要链表短一点,也就是hash冲突少一点,那么就需要减小hashmap的负载因子loadFacotor,当然这里也就扯远了,我们继续回到正题,

Student s1 = new Student(1, "小王", "123456");
Student s2 = new Student(1, "小王", "123456");

​ 对于s1和s2两个对象,如果我们我们已经将s1存入一个map对象,那么我们再存入s2时,我们希望的是这是不能再插入map了,因为此时map中已经存在小王这个对象了,那么如何才能做到呢

​ 首先我们通过s1的hashcode % capacity 得到了一个数组索引,然后将s1这个对象存入map,那么我们再插入s2的时候同样也需要计算它的hashcode,然后定位到相同的数组索引,然后判断该链表中是否存在小王这样一个对象,如果存在就不put

​ 所以我们需要得到的s1和s2的hashcode相同,才能避免同一个对象被put进入map中多次,所以我们才需要在重写equals()方法的同时重写equals()方法,让两个相等的对象具有相同的hashcode

​ 可能细心的盆友会发现如果我们只是需要简单的根据判断两个对象的内容是否相同来判断两个对象是否相等,而不涉及到ma'p操作,那么其实也是不用重写ha'shcode方法了,但是万一哪天突然不小心放进了map了呢,所以一般我们重写equals()方法的同时都会重写hashcode(),确保万无一失~

参考

重写equal()时为什么也得重写hashCode()之深度解读equal方法与hashCode方法渊源

重写equals方法后重写hashCode方法的必要性


flyingcr
44 声望4 粉丝