首先从源码的角度来看一看equals()方法和hashcode()方法含义
equals()方法和hashcode()方法都属于Object类,在Java中,所有的类都是Object类的子类,也就是说,任何Java对象都可调用Object类的方法。
- equals()方法
public boolean equals(Object obj) {
return (this == obj);
}
很明显,该方法就是用来判断两个对象是否是同一个对象。在Object类源码中,其底层是使用了“==”来实现,也就是说通过比较两个对象的内存地址是否相同判断是否是同一个对象,实际上,该equals()方法通常没有太大的实用价值。而我们往往需要用equals()来判断 2个对象在逻辑上是否等价,而非验证它的唯一性。这样我们在实现自己的类时,就要重写equals()方法。
- hashcode()方法:
public native int hashCode();
一提到hashcode,很自然就想到哈希表。将某一key值映射到表中的一个位置,从而达到以O(1)的时间复杂度来查询该key值。Object类源码中,hashCode()是一个native方法,哈希值的计算利用的是内存地址。
我们看一下Object类中关于hashCode()方法的注释
1,在Java运用程序执行期间,在对同一对象多次调用hashCode方法时,必须一致地返回相同的整数,前提是将对象进行equals比较时所用的信息没有被修改。从某一运用程序的一次执行到同一运用程序的另外一次执行,该整数无需保持一致。
2.如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
3.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
通俗的讲,注释中第二点和第三点的含义就是equals()和hashcode()方法要保持相当程度的一致性,equals()方法相等,hashcode()必须相等;反之,equals方法不相等,hashcode可以相等,可以不相等。但是两者的一致有利于提高哈希表的性能。
所以,源码注释来看,两方法的同时重写是很必要的。
实际来看,不同时重写将如何?
equals()相等的的两个等价对象因为hashCode不同,所以在hashmap中的table数组的下标不同,从而这两个对象就会同时存在于集合中,在调用hashmap集合中的方法时就会出现逻辑的错误,也就是,你的equals()方法也“白白”重写了。
因此,对于“为什么重写equals()就一定要重写hashCode()方法?”这个问题应该是有个前提,就是你需要用到HashMap,HashSet等Java集合。用不到哈希表的话,其实仅仅重写equals()方法也可以吧。而工作中的场景是常常用到Java集合,所以Java官方建议重写equals()就一定要重写hashCode()方法。
HashMap存储数据的时候,是取的key值的哈希值,然后计算数组下标,采用链地址法解决冲突,然后进行存储。取数据的时候,依然是先要获取到哈希值,找到数组下标,然后for遍历链表集合,进行比较是否有对应的key。比较关心的有两点:
无论是 put 还是 get 的时候,都需要得到key的哈希值,去定位key的数组下标;
在 get 的时候,需要调用equals方法比较是否有相等的key存储过。
package com.evan.springboot.study;
import java.util.HashMap;
/**
* @author evanYang
* @version 1.0
* @date 2020/5/13 下午 2:44
*/
public class Demo {
private int a;
public Demo(int a){
this.a=a;
}
public static void main(String[] args) {
HashMap<Demo, Integer> map = new HashMap<>();
Demo demo = new Demo(1);
map.put(demo,1);
Integer value = map.get(new Demo(1));
if(value!=null){
System.out.println(value);
}else{
System.out.println("value is null");
}
}
}
- 分析:
Map的key使我们自定义的类,这里没有重写equals()和hashcode()方法,我们打印一下hashcode看下
public static void main(String[] args) {
HashMap<Demo, Integer> map = new HashMap<>();
Demo demo = new Demo(1);
map.put(demo,1);
System.out.println("put 时的hashCode"+demo.hashCode());
Demo demo1 = new Demo(1);
System.out.println("get 时的hashCode"+demo1.hashCode());
Integer value = map.get(demo1);
if(value!=null){
System.out.println(value);
}else{
System.out.println("value is null");
}
}
hashCode不一致,由此而拿不到数据。这两个key,在Map计算的时候,数组下标可能就不一致,就算数据下标碰巧一致,根据前面,最后equals比较的时候也不可能相等(很显然这是两个对象,在堆上的地址必定不一样)。
- 现在我们不重写hashCode(),equals()方法
public static void main(String[] args) {
HashMap<Demo, Integer> map = new HashMap<>();
Demo demo = new Demo(1);
map.put(demo, 1);
System.out.println("put 时的hashCode" + demo.hashCode());
Demo demo1 = new Demo(1);
map.put(demo1, 2);
System.out.println("get 时的hashCode" + demo1.hashCode());
Integer value = map.get(demo);
System.out.println(value);
Integer value1 = map.get(demo1);
System.out.println(value1);
}
这里认为是同一个key
- 假如重写了equals方法,将这两个对象都put进去,根据map的原理,只要是key一样,后面的值会替换前面的值,实验如下
public static void main(String[] args) {
HashMap<Demo, Integer> map = new HashMap<>();
Demo demo = new Demo(1);
map.put(demo, 1);
System.out.println("put 时的hashCode" + demo.hashCode());
Demo demo1 = new Demo(1);
map.put(demo1, 2);
System.out.println("get 时的hashCode" + demo1.hashCode());
Integer value = map.get(demo);
System.out.println(value);
Integer value1 = map.get(demo1);
System.out.println(value1);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof Demo) {
return true;
}else {
return false;
}
}
同样的一个对象,为什么在map中存了两份,map的key值不是不能重复吗?
没错,它就是存的两份。只不过在它看来,这两个的key是不一样的,因为它们的哈希码就是不一样的,可以打印看下输出的hash码确实不一样。那怎么办?只有重写hashCode()方法,更改后的代码如下
public static void main(String[] args) {
HashMap<Demo, Integer> map = new HashMap<>();
Demo demo = new Demo(1);
map.put(demo, 1);
System.out.println("put 时的hashCode" + demo.hashCode());
Demo demo1 = new Demo(1);
map.put(demo1, 2);
System.out.println("get 时的hashCode" + demo1.hashCode());
Integer value = map.get(demo);
System.out.println(value);
Integer value1 = map.get(demo1);
System.out.println(value1);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof Demo;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + (a != null ? a.hashCode() : 0);
return result;
}
可以看到,它们的hash码是一致的,且最后的结果也是预期的。
总结:
Map中存了两个数值一样的key,这个问题很严重。所以在重写equals方法的时候,一定要重写hashCode方法。
类似HashMap、HashTable、HashSet这种的都要考虑到散列的数据类型的运用。