谈谈为什么重写equals要重写hashcode()方法
面试官可能会问你:“你重写过 hashCode()
和 equals()
么?为什么重写 equals()
时必须重写 hashCode()
方法?”
一个非常基础的问题,面试中的重中之重
开始的时候,我十分不理解,两个毫无相干的东西为什么要联系在一起?这篇文章就是研究过程,可能会借鉴一些网上的内容
首先来看
hashCode() 有什么用?
hashCode()
的作用是获取哈希码(int
整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。
hashCode()
定义在 JDK 的 Object
类中,这就意味着 Java 中的任何类都包含有 hashCode()
函数。另外需要注意的是: Object
的 hashCode()
方法是本地方法,也就是用 C 语言或 C++ 实现的,该方法通常用来将对象的内存地址转换为整数之后返回。
public native int hashCode();
重点是Object
的hashcode()
是为了将对象的内存地址转换为整数,如果不重写hashcode,即其他所有的对象基本默认都是继承这个方法。
回顾equals方法
回顾一下 Object的equals方法 实现,并简单汇总一下使用equals
方法的规律。
public boolean equals(Object obj) {
return (this == obj);
}
通过上面Object
的源代码,可以得出一个结论:如果一个类未重写equals
方法,那么本质上通过“==”和equals
方法比较的效果是一样的,都是比较两个对象的的内存地址。
前面两篇文章讲到String
和Integer
在比较时的区别,关键点也是它们对equals
方法的实现。
面试时总结一下就是:默认情况下,从Object
类继承的equals
方法与“==”完全等价,比较的都是对象的内存地址。但我们可以重写equals
方法,使其按照需要进行比较,如String
类重写了equals
方法,比较的是字符的序列,而不再是内存地址。
即这时,String类也肯定重写了hashcode()
方法,因为ta重写了equals()
;
与hashCode方法的关系
那么equals
方法与hashCode
方法又有什么关系呢?我们来看Object上equals
方法的一段注释。
大致意思是:当重写equals
方法后有必要将hashCode
方法也重写,这样做才能保证不违背hashCode
方法中“相同对象必须有相同哈希值”的约定。
此处只是提醒了我们重写hashCode
方法的必要性,那其中提到的hashCode
方法设计约定又是什么呢?相关的内容定义在hashCode
方法的注解部分。
hashCode方法约定
关于hashCode
方法的约定原文比较多,大家直接看源码即可看到,这里汇总一下,共三条:
(1)如果对象在使用equals
方法中进行比较的参数没有修改,那么多次调用一个对象的hashCode()
方法返回的哈希值应该是相同的。
(2)如果两个对象通过equals
方法比较是相等的,那么要求这两个对象的hashCode
方法返回的值也应该是相等的。
(3)如果两个对象通过equals
方法比较是不同的,那么也不要求这两个对象的hashCode
方法返回的值是不相同的。但是我们应该知道对于不同对象产生不同的哈希值对于哈希表(HashMap等)能够提高性能。
其实,看到这里我们了解了hashCode
的实现规约,但还是不清楚为什么实现equals
方法需要重写hashCode
方法。但我们可以得出一条规律:
hashCode
方法实际上必须要完成的一件事情就是,为equals
方法认定为相同的对象返回相同的哈希值。
这里举一个实例
public void test1() {
String s = "ok";
StringBuilder sb = new StringBuilder(s);
System.out.println(s.hashCode() + " " + sb.hashCode());
String t = new String("ok");
StringBuilder tb = new StringBuilder(s);
System.out.println(t.hashCode() + " " + tb.hashCode());
}
上面这段代码打印的结果为:
3548 1833638914
3548 1620303253
String
实现了hashCode
方法,而StringBuilder
并没有实现,这就导致即使值是一样的,hashCode
也不同。
下面我们以HashMap
为例,看看如果没有实现hashCode
方法会导致什么严重的后果。
@Test
public void test2() {
String hello = "hello";
Map<String, String> map1 = new HashMap<>();
String s1 = new String("key");
String s2 = new String("key");
map1.put(s1, hello);
System.out.println("s1.equals(s2):" + s1.equals(s2));
System.out.println("map1.get(s1):" + map1.get(s1));
System.out.println("map1.get(s2):" + map1.get(s2));
Map<Key, String> map2 = new HashMap<>();
Key k1 = new Key("A");
Key k2 = new Key("A");
map2.put(k1, hello);
System.out.println("k1.equals(k2):" + s1.equals(s2));
System.out.println("map2.get(k1):" + map2.get(k1));
System.out.println("map2.get(k2):" + map2.get(k2));
}
class Key {
private String k;
public Key(String key) {
this.k = key;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Key) {
Key key = (Key) obj;
return k.equals(key.k);
}
return false;
}
}
实例中定义了内部类Key
,其中实现了equals
方法,但未实现hashCode
方法。存放于Map
中的value
值都是字符串“hello”。
代码分两段,第一段演示当Map
的key
通过实现了hashCode
的String
时是什么效果;第二段演示了当Map
的key
通过未实现hashCode
方法的Key
对象时是什么效果。
执行上述代码,打印结果如下:
s1.equals(s2):true
map1.get(s1):hello
map1.get(s2):hello
k1.equals(k2):true
map2.get(k1):hello
map2.get(k2):null
分析结果可以看出,对于String
作为key
的 s1 和 s2 来说,通过equals
比较相等是自然的,获得的值也是相同的。但 k1 和 k2 通过equals
比较是相等,但为什么在Map
中获得的结果却不一样?本质上就是因为没有重写hashCode
方法导致Map
在存储和获取过程中调用hashCode
方法获得的值不一致。
此时在Key
类重写继承自Objecd的hashCode
方法:
@Override
public int hashCode(){
return k.hashCode();
}
再次执行,便可正常获得对应的值。
s1.equals(s2):true
map1.get(s1):hello
map1.get(s2):hello
k1.equals(k2):true
map2.get(k1):hello
map2.get(k2):hello
通过上面的典型实例演示了不重写hashCode
方法的潜在后果。
即,如果不重写已经经过重写equals()
的类的hashcode()
方法,就不能保证当equals()
返回结果是true,hashcode()
也会返回相同值。
重写hashCode方法
了解了重写hashCode
方法的重要性,也了解了对应的规约,那么下面我们就聊聊如何优雅的重写hashCode
方法。
首先,如果使用IDEA
的话,那么直接使用快捷键即可。
生成的效果如下:
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Key key = (Key) o;
return Objects.equals(k, key.k);
}
@Override
public int hashCode() {
return Objects.hash(k);
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Key key = (Key) o;
return Objects.equals(k, key.k);
}
@Override
public int hashCode() {
return Objects.hash(k);
}
### 引用材料来之https://www.w3cschool.cn/article/4adce89bf8b821.html