1,equals()方法和hashcode()这两个方法是用来干什么的?
在Java中的任意类都是继承自Object,equals方法和hashcod方法这两个方法便是继承自Object,因此任意类都需要继承、实现equals方法,并且实现hashcode方法。任意类对象都持有这两个对象成员方法。
(1) equals方法其实很容易理解,它是是判断两个对象是否相等!
-
在object中的默认实现(直接使用==比较两个对象):
* @param obj the reference object with which to compare. * @return {@code true} if this object is the same as the obj * argument; {@code false} otherwise. * @see #hashCode() * @see java.util.HashMap */ public boolean equals(Object obj) { return (this == obj); } -
在String等包装类中的重写后的实现:
* @param anObject * The object to compare this {@code String} against * * @return {@code true} if the given object represents a {@code String} * equivalent to this string, {@code false} otherwise * * @see #compareTo(String) * @see #equalsIgnoreCase(String) */ public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } -
在我们自定义的类中也可以自定义对象相等的实现,通过重写equals方法。比如自定义该类型对象恒等。
public class Same {
@Override public boolean equals(Object obj) { return true; }}
注:==和equals方法的不同。
判断对象是否相等的实现维度不同。==是通过语言的编译器层面去实现,在最终的运行时会解释为比较两个对象的地址。而equals方法则是是jdk代码层面的实现,对于程序员来说更加灵活。
(2) hashcode方法是用来计算对象的hashcode这一特殊值,它存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的(用HashCode来代表对象就是在hash表中的位置).
-
hashcode方法在object类中是没有默认实现的。
* @return a hash code value for this object. * @see java.lang.Object#equals(java.lang.Object) * @see java.lang.System#identityHashCode */ public native int hashCode(); -
hashcode方法在String类中的实现
private int hash; // Default to 0 /** * Returns a hash code for this string. The hash code for a * {@code String} object is computed as * <blockquote><pre> * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] * </pre></blockquote> * using {@code int} arithmetic, where {@code s[i]} is the * <i>i</i>th character of the string, {@code n} is the length of * the string, and {@code ^} indicates exponentiation. * (The hash value of the empty string is zero.) * * @return a hash code value for this object. */ public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
可以看出每个对象都可以通过hashcode方法算出一个hashcode值,这个值一定程度上对不同对象做了区分,但是它不能完全区分两个对象,不同的对象的hashcode值有可能相等。
同时我们可以得出两个结论:如果两个对象相同,那么它们的hashCode值一定要相同;如果两个对象的hashCode相同,它们并不一定相同。
貌似这个hashcode远不如equals方法准确,那为啥需要它?在一个集合中去查找一个对象的场景下,它能加速查找是hashcode方法存在的一个重要原因。如果我们只用equals方法势必需要挨个去比较。而我们通过hashcode可以在HashMap、HashSet等利用hashcode来加速查找,然后再使用equals方法来查到最终的目标对象。
2,为什么重写了equals()方法必须重写hashcode()方法?
1),如果我们理解了hashcode的存在意义,就很好理解为什么。一般来说,我们重写equals方法的目标是自定义对象相等的条件,这个时候如果我们不重写hashcode方法,就有可能导致equals方法判定为相等的对象的hashcode值不同,那么在使用hashcode加速查找的场景,如在HashMap、HashSet等集合中使用hashcode进行加速查找时就会发现找不到对象的情况,就发生了错误,因此我们约定在重写equals方法时也重写hashcode方法。
2)因为我们需要满足,如果两个对象相同,那么它们的hashCode值一定要相同;这样hashcode才能发挥它的作用。
我们可以看看HashMap中是如何使用hashcode和equals方法的。这里以put方法的实现为例。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
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;
}
}
其实无论是put方法还是get方法,都是先利用hashcode值的散列值找到一个hash表的槽位后再使用equals方法来找到最终的目标。如果我们重写了equals没有重写hashcode,相同的对象的hashcode不同,那很容易就出现查不到对应槽位的情况。