在逛 programcreek 的时候,我发现了一些专一细节但价值连城的主题。好比说:Java 的 equals()
和 hashCode()
是远房亲戚吗?像这类灵魂拷问的主题,很是值得深刻地研究一下。html
另外,我想要告诉你们的是,研究的过程很是的有趣,就好像在迷宫里探宝同样,起初有些不知所措,但通过一番用心的摸索后,不但会找到宝藏,还会有一种茅塞顿开的感受,很是棒。java
对于绝大多数的初级程序员或者说不重视“内功”的老鸟来讲,每每停留在“知其然不知其因此然”的层面上——会用,但要说底层的原理,可就只能挠挠头双手一摊一张问号脸了。node
很长一段时间内,我,沉默王二也一直处于这种层面上。但我决定改变了,由于“内功”就好像是在打地基,只有把地基打好了,才能盖起经得住考验的高楼大厦。借此机会,我就和你们一块儿,对“equals() 和 hashCode()”进行一次深刻地研究。程序员
equals()
和 hashCode()
是 Java 的超级祖先类 Object 定义的两个重要的方法:数组
public boolean equals(Object obj) public int hashCode() 复制代码
讲道理,单从方法的定义上来看,equals()
和 hashCode()
这两个方法之间没有任何亲戚关系,远房都够不上资格。但往深处扒拉,它们之间还真的是有千丝万缕的关系。究竟是什么关系呢?若是你们伙比较感兴趣的话,就请随我来,打怪进阶喽!bash
为了勾起你们的好奇心,我特地编写了下面这个示例。ide
public class Cmower {
private String name;
public Cmower(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (!(obj instanceof Cmower))
return false;
if (obj == this)
return true;
return this.name.equals(((Cmower) obj).name);
}
public static void main(String[] args) {
Cmower a1 = new Cmower("沉默王二");
Cmower a2 = new Cmower("沉默王三");
Map<Cmower, Integer> m = new HashMap<Cmower, Integer>();
m.put(a1, 18);
m.put(a2, 28);
System.out.println(m.get(new Cmower("沉默王二")));
}
}
复制代码
咱们定义了一个 Cmower 类,它有一个字段 name;而后咱们重写了 equals()
方法:ui
1)若是指定对象为 null,返回 false;this
2)若是指定对象的类型不是 Cmower 类,返回 false;spa
3)若是指定对象“==”当前对象,返回 true;
4)若是指定对象的 name 和当前对象的 name 相等,返回 true;意味着只要 name 相等,两个对象就是 equals 的。
在 main 方法中,咱们建立了两个 Cmower 类型的对象,name 分别为“沉默王二”和“沉默王三”,并将它们做为键放入了 HashMap 当中;按理说,只要键的 name 为“沉默王二”,程序就应该可以获取咱们以前放入的 Cmower 对象。但结果却“出人意料”:
null
复制代码
可明明 HashMap 中放入了“沉默王二”啊,debug 也能够证实这一点。
那到底是哪里出了错呢?
开门见山地说吧,问题出在 hashCode()
身上,Cmower 类没有重写该方法。借此机会交代一下 equals()
和 hashCode()
这两个方法之间的关系吧:
1)若是两个对象须要相等(equals),那么它们必须有着相同的哈希码(hashCode);
2)但若是两个对象有着相同的哈希码,它们却不必定相等。
这就好像在说:你和你对象要想在一块儿天长地久,就必须彼此相爱;但即使你和你对象彼此相爱,但却不必定真的能在一块儿。
(扎心了,老铁)
HashMap 之因此可以更快地经过键获取对应的值,是由于它的键位上使用了哈希码。当咱们须要从 HashMap 中获取一个值的时候,会先把键转成一个哈希码,判断值所在的位置;而后在使用“==”操做符或者 equals()
方法比较键位是否相等,从而取出键位上的值。
能够查看一下 HashMap 类的 get()
方法的源码。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
复制代码
在上例中,之因此没有从 HashMap 中取出 name 为“沉默王二”的 Cmower 对象,就是由于 put 的时候和 get 的时候两个对象的哈希码不一样的缘由形成的。
明白了缘由以后,咱们就能够对 Cmower 类进行改造,来看重写后的 hashCode()
吧。
@Override
public int hashCode() {
return this.name.hashCode();
}
复制代码
重写后的 hashCode()
方法体很是简单:返回 name 字段的哈希码。这样的话,put 和 get 用到的哈希码就是相同的,由于“沉默王二”的哈希码是 867758096。再次运行程序,你就会发现输出结果再也不是 null 而是 18 了。
1)equals()
的做用是用来判断两个对象是否相等。
2)hashCode()
的做用是获取对象的哈希码;哈希码通常是一个整数,用来肯定对象在哈希表(好比 HashMap)中的索引位置。
拿 HashMap 来讲,它本质上是经过数组实现的。当咱们要获取某个“值”时,其实是要获取数组中的某个位置的元素。而数组的位置,就是经过“键”来获取的;更进一步说,是经过“键”对应的哈希码计算获得的。
3)若是两个对象须要相等(equals),那么它们必须有着相同的哈希码(hashCode);
4)但若是两个对象有着相同的哈希码,它们却不必定相等。
来对照一下官方给出的关于 equals()
和 hashCode()
的解释:
equals(Object)
method, then calling the hashCode
method on each of the two objects must produce the same integer result.equals(java.lang.Object)
method, then calling the hashCode
method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.可能有读者会问:“必定要同时重写 equals()
和 hashCode()
吗?”
回答固然是否认的。若是对象做为键在哈希表中,那么两个方法都要重写,由于 put 和 get 的时候须要用到哈希码和 equals()
方法;
若是对象不在哈希表中,仅用来判断是否相等,那么重写 equals()
就好了。
嘿嘿😜,这下搞清楚 equals()
和 hashCode()
之间的关系了吧!
好了,读者朋友们,以上就是本文的所有内容了。能看到这里的都是最优秀的程序员,升职加薪就是你了👍。原创不易,若是以为有点用的话,请不要吝啬你手中点赞的权力——由于这将是我写做的最强动力。