覆寫equals方法必須覆寫hashCode方法,是JDK API上反復說明的,不過為什么要這樣做呢?這兩個方法之間有什么關系呢?
void test() { // Person類的實例作為Map的key Map<Person, Object> map = new HashMap<Person, Object>(); map.put(new Person("張三"), new Object()); // Person類的實例作為List的元素 List<Person> list = newArrayList<Person>() list.add(new Person("張三")); // 列表中是否包含 boolean b1 = list.contains(new Person("張三")); // Map中是否包含 boolean b2 = map.containsKey(new Person("張三")); System.out.println(b1 + "|" + b2); }
Person類:
public class Person { private String name; private String age; public Person(String name) { setName(name); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public boolean equals(Object o) { /* * 使用getClass是為了防止子類被判斷為父類的情況 * 例如:Person中覆寫equals方法 {return name.equals(p.getName);} * 子類Employee繼承Person,此時子類中的euqals方法super.equlas(p); * 此時如果使用instanceof關鍵字,因為Employee是Person的子類,所以返回true * 進一步調用equals返回的是true,所以會將Person和Employee認為是同一人,顯然不合理 */ if(o != null && o.getClass() == this.getClass()) { Person p = (Person) o; if(p.getName() == null && this.name == null) return false; return this.name.equals(p.getName()); } return false; } }
我們先來看b1,Person類的equals覆寫了,不再判斷兩個地址是否相等,而是根據人員的姓名來判斷兩個對象是否相等,所以不管我們的 new Person(“張三”)產生了多少個對象,它們都是相等的。把“張三”對象放入List中,再檢查List中是否包含,那結果肯定是true了。
接着來看b2,我們把張三這個對象作為了Map的鍵(Key),放進去的對象是張三,檢查的對象還是張三,那應該和List的結果相同了,但是很遺憾,結果是false。原因何在呢?
原因就是HashMap的底層處理機制是以數組的方式保存Map條目(Map Entry)的,這其中的關鍵是這個數組下標的處理機制:依據傳入元素hashCode方法的返回值決定其數組的下標,如果該數組位置上已經有了Map條目,且與傳入的鍵值相等則不處理,若不相等則覆蓋;如果數組位置沒有條目,則插入,並加入到Map條目的鏈表中。同理,檢查鍵是否存在也是根據哈希碼確定位置,然后遍歷查找鍵值的。
接着深入探討,那對象元素的hashCode方法返回的是什么值呢?它是一個對象的哈希碼,是由Object類的本地方法生成的,確保每個對象有一個哈希碼(這也是哈希算法的基本要求:任意輸入k,通過一定算法f(k),將其轉換為非可逆的輸出,對於兩個輸入k1和k2,要求若k1=k2,則必須 f(k1)=f(k2),但也允許k1≠k2,f(k1)=f(k2)的情況存在)。
那回到我們的例子上,由於我們沒有重寫hashCode方法,兩個張三對象的hashCode方法返回值(也就是哈希碼)肯定是不相同的了,在HashMap的數組中也就找不到對應的Map條目了,於是就返回了false。
問題清楚了,修改也非常簡單,重寫一下hashCode方法即可,代碼如下:
public int hashCode() { //其中HashCodeBuilder是org.apache.commons.lang.builder包下的一個哈希碼生成工具, //使用起來非常方便,諸位可以直接在項目中集成。 return new HashCodeBuilder().append(name).toHashCode(); }
其中HashCodeBuilder是org.apache.commons.lang.builder包下的一個哈希碼生成工具,使用起來非常方便,諸位可以直接在項目中集成。(為什么不直接寫hashCode方法?因為哈希碼的生成有很多種算法,自己寫麻煩,事兒又多,所以采用拿來主義是最好的方法。)