產生它的散列碼

出自 《java puzzle》


本謎題試圖從前一個謎題中吸取教訓。下面的程序還是由一個Name類和一個main方法構成,這個main方法還是將一個名字放置到一個散列集合中,然後檢查該集合是否包含了這個名字。然而,這一次Name類已經覆寫了hashCode方法。那麼下面的程序將打印出什麼呢?

import java.util.*;
public class Name {
private String first, last;
public Name(String first, String last) {
this.first = first; this.last = last;
}
public boolean equals(Name n) {
return n.first.equals(first) && n.last.equals(last);
}
public int hashCode() {
return 31 * first.hashCode() + last.hashCode();
}
public static void main(String[ ] args) {
Set s = new HashSet();
s.add(new Name("Donald", "Duck"));
System.out.println(
s.contains(new Name("Donald", "Duck")));
}
}


該程序的main方法創建了兩個Name實例,它們表示的是相同的名字。這一次使用的名字是Donald Duck而不是Mickey Mouse,但是它們不應該有很大的區別。main方法同樣還是將第一個實例置於一個散列集合中,然後檢查該集合中是否包含了第二個實例。這一次hashCode方法明顯是正確的,因此看起來該程序應該打印true。但是,表象再次欺騙了我們:它總是打印出false。這一次又是哪裏出錯了呢?

在本謎題中,Name覆寫了hashCode方法,但是沒有覆寫equals方法。這並不是說Name沒有聲明一個equals方法,它確實聲明瞭,但是那是個錯誤的聲明。Name類聲明瞭一個參數類型是Name而不是Object的equals方法。這個類的作者可能想要覆寫equals方法,但是卻錯誤地重載了它[JLS 8.4.8.1, 8.4.9]。

HashSet類是使用equals(Object)方法來測試元素的相等性的;Name類中聲明一個equals(Name)方法對HashSet不造成任何影響。那麼Name是從哪裏得到了它的equals(Object)方法的呢?它是從Object哪裏繼承而來的。這個方法只有在它的參數與在其上調用該方法的對象完全相同時才返回true。我們的程序中的main方法將一個Name實例插入到了散列集合中,並且測試另一個實例是否存在於該散列集合中,由此可知該測試一定是返回false的。對我們而言,兩個實例可以代表那令人驚奇的水禽唐老鴨,但是對散列映射表而言,它們只是兩個不相等的對象。
訂正該程序只需用覆寫的equals方法來替換重載的equals方法即可。通過使用這個equals方法,該程序就可以打印出我們所期望的true:
public boolean equals(Object o) {
if (!(o instanceof Name))
return false;
Name n = (Name)o;
return n.first.equals(first) && n.last.equals(last);
}

要讓該程序可以正常工作,你只需增加一個覆寫的equals方法即可。你不必剔除那個重載的版本,但是你最好是刪掉它。重載爲錯誤和混亂提供了機會[EJ Item 26]。如果兼容性要求強制你必須保留一個自身類型的equals方法,那麼你應該用自身類型的重載去實現Object的重載,以此來確保它們具有相同的行爲:
public boolean equals(Object o) { return o instanceof Name && equals((Name) o); }

本謎題的教訓是:當你想要進行覆寫時,千萬不要進行重載。爲了避免無意識地重載,你應該機械地對你想要覆寫的每一個超類方法都拷貝其聲明,或者更好的方式是讓你的IDE幫你去做這些事。這樣做除了可以保護你免受無意識的重載之害,而且還可以保護你免受拼錯方法名之害。如果你使用的5.0或者更新的版本,那麼對於那些意在覆寫超類方法的方法,你可以將@Override註釋應用於每一個這樣的方法的聲明上:

@Override public Boolean equals(Object o) { ... }

在使用這個註釋時,除非被註釋的方法確實覆寫了一個超類方法,否則它將不能編譯。對語言設計者來說,值得去考慮在每一個覆寫超類方法的方法聲明上都添加一個強制性的修飾符。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章