问题导入
众所周知,当使用自定义的类作为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