首先,equals()方法和hashCode()方法都來自於Object類的定義,Java類都繼承了這兩個方法,都定義了本身的實現。java
equlas()方法的正確理解應該是:判斷兩個對象是否相等。那麼判斷對象相等的標尺又是什麼?算法
在object類中,此標尺即爲==。ide
public boolean equals(Object obj) { return (this == obj); }
固然,這個標尺不是固定的,其餘類中能夠按照實際的須要對此標尺含義進行重定義。如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()方法來從新定義此標尺,那麼默認的將是其父類的equals(),直到Object基類。ui
以下場景的實際業務需求,對於User bean,由實際的業務需求可知當屬性uid相同時,表示的是同一個User,即兩個User對象相等。則能夠重寫equals以重定義User對象相等的標尺。this
package com.corn.objectsummary; public class User { private int uid; private String name; private int age; public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } protected String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof User)) { return false; } if (((User) obj).getUid() == this.getUid()) { return true; } return false; } }
package com.corn.objectsummary; public class ObjectTest implements Cloneable { public static void main(String[] args) { User u1 = new User(); u1.setUid(111); u1.setName("張三"); User u2 = new User(); u2.setUid(111); u2.setName("張三丰"); System.out.println(u1.equals(u2)); //返回true } }
ObjectTest中打印出true,由於User類定義中重寫了equals()方法,這很好理解,極可能張三是一我的小名,張三丰纔是其大名,判斷這兩我的是否是同一我的,這時只用判斷uid是否相同便可。編碼
重寫equals()方法遵循的準則:spa
對稱性:若是x.equals(y)返回是「true」,那麼y.equals(x)也應該返回是「true」。code
反射性:x.equals(x)必須返回是「true」。對象
類推性:若是x.equals(y)返回是「true」,並且y.equals(z)返回是「true」,那麼z.equals(x)也應該返回是「true」。
還有一致性:若是x.equals(y)返回是「true」,只要x和y內容一直不變,無論你重複x.equals(y)多少次,返回都是「true」。
任何狀況下,x.equals(null),永遠返回是「false」;x.equals(和x不一樣類型的對象)永遠返回是「false」。
如上重寫equals方法表面上看上去是能夠了,實則否則。由於它破壞了Java中的約定:重寫equals()方法必須重寫hasCode()方法。
hashCode():方法返回一個整形數值,表示該對象的哈希碼值。
hashCode()具備以下約定:
1).在Java應用程序程序執行期間,對於同一對象屢次調用hashCode()方法時,其返回的哈希碼是相同的,前提是將對象進行equals比較時所用的標尺信息未作修改。在Java應用程序的一次執行到另一次執行,同一對象的hashCode()返回的哈希碼無須保持一致;
2).若是兩個對象相等(依據:調用equals()方法),那麼這兩個對象調用hashCode()返回的哈希碼也必須相等;
3).反之,兩個對象調用hasCode()返回的哈希碼相等,這兩個對象不必定相等。
即嚴格的數學邏輯表示爲: 兩個對象相等 <=> equals()相等 => hashCode()相等。所以,重寫equlas()方法必須重寫hashCode()方法,以保證此邏輯嚴格成立,同時能夠推理出:hasCode()不相等 => equals()不相等 <=> 兩個對象不相等。
可能有人在此產生疑問:既然比較兩個對象是否相等的惟一條件(也是次要條件)是equals,那麼爲何還要弄出一個hashCode(),而且進行如此約定,弄得這麼麻煩?
其實,這主要體如今hashCode()方法的做用上,其主要用於加強哈希表的性能。
以集合類中,以Set爲例,當新加一個對象時,須要判斷現有集合中是否已經存在與此對象相等的對象,若是沒有hashCode()方法,須要將Set進行一次遍歷,並逐一用equals()方法判斷兩個對象是否相等,此種算法時間複雜度爲o(n)。經過藉助於hasCode方法,先計算出即將新加入對象的哈希碼,而後根據哈希算法計算出此對象的位置,直接判斷此位置上是否已有對象便可。(注:Set的底層用的是Map的原理實現)
在此須要糾正一個理解上的誤區:對象的hashCode()返回的不是對象所在的物理內存地址。甚至也不必定是對象的邏輯地址,hashCode()相同的兩個對象,不必定相等,換言之,不相等的兩個對象,hashCode()返回的哈希碼可能相同。
所以,在上述代碼中,重寫了equals()方法後,須要重寫hashCode()方法。
package com.corn.objectsummary; public class User { private int uid; private String name; private int age; public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } protected String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof User)) { return false; } if (((User) obj).getUid() == this.getUid()) { return true; } return false; } @Override public int hashCode() { int result = 17; result = 31 * result + this.getUid(); return result; } }
注:上述hashCode()的重寫中出現了result*31,是由於result*31 = (result<<5) - result。之因此選擇31,是由於左移運算和減運算計算效率遠大於乘法運算。固然,也能夠選擇其餘數字。