Java庫(kù)謎題57:名字里有什么

字號(hào):

下面的程序包含了一個(gè)簡(jiǎn)單的不可變類(lèi),它表示一個(gè)名字,其main方法將一個(gè)名字置于一個(gè)集合中,并檢查該集合是否確實(shí)包含了該名字。那么,這個(gè)程序到底會(huì)打印出什么呢?
    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(Object o) {
     if (!(o instanceof Name))
     return false;
     Name n = (Name)o;
     return n.first.equals(first) && n.last.equals(last);
     }
     public static void main(String[] args) {
     Set s = new HashSet();
     s.add(new Name("Mickey", "Mouse"));
     System.out.println(
     s.contains(new Name("Mickey", "Mouse")));
     }
    }
    一個(gè)Name實(shí)例由一個(gè)姓和一個(gè)名構(gòu)成。兩個(gè)Name實(shí)例在通過(guò)equals方法進(jìn)行計(jì)算時(shí),如果它們的姓相等且名也相等,則這兩個(gè)Name實(shí)例相等。姓和名是用在String中定義的equals方法來(lái)比較的,兩個(gè)字符串如果以相同的順序包含相同的若干個(gè)字符,那么它們就相等。因此,兩個(gè)Name實(shí)例如果表示相同的名字,那么它們就相等。例如,下面的方法調(diào)用將返回true:
    new Name("Mickey", "Mouse").equals(new Name("Mickey", "Mouse"))
    該程序的main方法創(chuàng)建了兩個(gè)Name實(shí)例,它們都表示Mickey Mouse。該程序?qū)⒌谝粋€(gè)實(shí)例放置到了一個(gè)散列集合中,然后檢查該集合是否包含第二個(gè)實(shí)例。這兩個(gè)Name實(shí)例是相等的,因此看起來(lái)該程序似乎應(yīng)該打印true。如果你運(yùn)行它,幾乎可以肯定它將打印false。那么這個(gè)程序出了什么問(wèn)題呢?
    這里的bug在于Name違反了hashCode約定。這看起來(lái)有點(diǎn)奇怪,因?yàn)镹ame連hashCode都沒(méi)有,但是這確實(shí)是問(wèn)題所在。Name類(lèi)覆寫(xiě)了equals方法,而hashCode約定要求相等的對(duì)象要具有相同的散列碼。為了遵守這項(xiàng)約定,無(wú)論何時(shí),只要你覆寫(xiě)了equals方法,你就必須同時(shí)覆寫(xiě)hashCode方法[EJ Item 8]。
    因?yàn)镹ame類(lèi)沒(méi)有覆寫(xiě)hashCode方法,所以它從Object那里繼承了其hashCode實(shí)現(xiàn)。這個(gè)實(shí)現(xiàn)返回的是基于標(biāo)識(shí)的散列碼。換句話說(shuō),不同的對(duì)象幾乎總是產(chǎn)生不相等的散列值,即使它們是相等的也是如此。所以說(shuō)Name沒(méi)有遵守hashCode的約定,因此包含Name元素的散列集合的行為是不確定的。
    當(dāng)程序?qū)⒌谝粋€(gè)Name實(shí)例放置到散列集合中時(shí),該集合就會(huì)在某個(gè)散列位置上放置這個(gè)實(shí)例對(duì)應(yīng)的項(xiàng)。該集合是基于實(shí)例的散列值來(lái)選擇散列位置的,這個(gè)散列值是通過(guò)實(shí)例的hashCode方法計(jì)算出來(lái)的。