面试官让你说说 == 和 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 方法的必要性