【Java】为什么重写equals一定要重写hashcode详解编程语言

问题导入

众所周知,当使用自定义的类作为map的键的时候,需要重写equals和hashcode方法,但是为什么
一定要这么做呢?这里面是牵扯到map的实现,以及Object类的equals()和hashcode()方法的。

问题探究

首先看看Object类的equals和hashcode的源码。

equals():

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

hashCode():

public native int hashCode(); 

可以看出,equals方法比较的是两个对象的内存地址,hashCode()方法是一个native方法,用C++写的,作用是返回一个由对象生成的无规律散列值。

假设现在有一个Person类,有name和age参数。那么将equals和hashCode重写后是这样的:

public class Person{
    
    String name; 
    int age; 
     
    @Override 
    public boolean equals(Object o) {
    
        if (this == o) return true; 
        if (o == null || getClass() != o.getClass()) return false; 
        Person person= (Person) o; 
        return age == person.age && 
                name.equals(person.name); 
    } 
 
    @Override 
    public int hashCode() {
    
        return Objects.hash(name, age); 
    } 
} 

其中重写的hashcode()中的Objects.hash()方法调用了Arrays.hashCode()方法:

/* 
Objects.hash() 
*/ 
public static int hash(Object... values) {
    
        return Arrays.hashCode(values); 
} 
 
/* 
Arrays.hashCode() 
*/ 
public static int hashCode(Object a[]) {
    
    if (a == null) 
        return 0; 
 
    int result = 1; 
 
    for (Object element : a) 
        result = 31 * result + (element == null ? 0 : element.hashCode()); 
 
    return result; 
} 

由重写后的方法可以看出:默认的Object.equals()方法只是比较对象的地址,重写后比较的是对象的值。

对于需要保证键唯一性的HashSet、HashMap来说,对象之间进行比较的equals方法尤为重要,这个毋庸置疑,但为什么还要重写hashCode()呢?
我还是从HashMap的源码里面找到了答案。
这里截取一段HashMap的put代码,看看就明白了。代码有点多,重点部分在if中key的比较。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 
boolean evict) {
 
Node<K,V>[] tab; Node<K,V> p; int n, i; 
if ((tab = table) == null || (n = tab.length) == 0) 
n = (tab = resize()).length; 
if ((p = tab[i = (n - 1) & hash]) == null) 
tab[i] = newNode(hash, key, value, null); 
else {
 
Node<K,V> e; K k; 
if (p.hash == hash && 
((k = p.key) == key || (key != null && key.equals(k)))) 
e = p; 
else if (p instanceof TreeNode) 
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 
else {
 
for (int binCount = 0; ; ++binCount) {
 
if ((e = p.next) == null) {
 
p.next = newNode(hash, key, value, null); 
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 
treeifyBin(tab, hash); 
break; 
} 
if (e.hash == hash && 
((k = e.key) == key || (key != null && key.equals(k)))) 
break; 
p = e; 
} 
} 
if (e != null) {
 // existing mapping for key 
V oldValue = e.value; 
if (!onlyIfAbsent || oldValue == null) 
e.value = value; 
afterNodeAccess(e); 
return oldValue; 
} 
} 
++modCount; 
if (++size > threshold) 
resize(); 
afterNodeInsertion(evict); 
return null; 
} 

比如这一行,if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break;
它比较了key的hash值,并且还进行了equals比较,于是重写equals必须重写hashcode方法。重写hashcode方法,它的返回值同样是根据对象的参数值来生成的,同样的参数值返回相同的hashcode。

实例分析

我自己也做了测试。

Person类就是上述的Person类,有age和name两个字段。

然后是测试main方法:

public static void main(String[] args) {
 
HashMap<Person, Integer> map = new HashMap<>(); 
map.put(new Person(1, "小明"), 1); 
map.put(new Person(1, "小明"), 2); 
for( Person p : map.keySet() ){
 
System.out.println(p.toString() + " " + map.get(p)); 
} 
} 

我put了两个相同key值的键值对,正常结果是第二个覆盖第一个的值。
但是当我没有对Person重写equals和hashcode时,结果是这样的:
在这里插入图片描述
只有重写了equals和hashcode时,结果才正确:
正确结果
这就印证了上述说法。

补充:以下是关于hashcode的一些规定:

  • 两个对象相等,hashcode一定相等

  • 两个对象不等,hashcode不一定不等

  • hashcode相等,两个对象不一定相等

  • hashcode不等,两个对象一定不等

原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/19388.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论