Java 用自定義類型做爲HashMap的鍵

這是Java中很經典的問題,在面試中也常常被問起。其實不少書或者文章都提到過要重載hashCode()equals()兩個方法才能實現自定義鍵在HashMap中的查找,可是爲何要這樣以及若是不這樣作會產生什麼後果,好像不多有文章講到,因此寫這麼一篇來講明下。java

首先,若是咱們直接用如下的Person類做爲鍵,存入HashMap中,會發生髮生什麼狀況呢?面試

javapublic class Person {

    private String id;

    public Person(String id) {
        this.id = id;
    }
}
javaimport java.util.HashMap;

public class Main {
    public static void main(String[] args) {

        HashMap<Person, String> map = new HashMap<Person, String>();

        map.put(new Person("001"), "findingsea");
        map.put(new Person("002"), "linyin");
        map.put(new Person("003"), "henrylin");
        map.put(new Person("003"), "findingsealy");

        System.out.println(map.toString());

        System.out.println(map.get(new Person("001")));
        System.out.println(map.get(new Person("002")));
        System.out.println(map.get(new Person("003")));
    }
}

那麼輸出結果是什麼呢?ide

{Person@6e4d4d5e=henrylin, Person@275cea3=findingsea, Person@15128ee5=findingsealy, Person@4513098=linyin}
null
null
null

咱們能夠看到,這裏出現了兩個問題:this

  1. 在添加的過程當中,咱們將key=new Person("003")的鍵值對添加了兩次,那麼在指望中,HashMap中應該只存在一對這樣的鍵值對,由於key(指望中)是相同的,因此不該該重複添加,第二次添加的value="findingsealy"應該替換掉原先的value="henrylin"。可是在輸入中,咱們發現指望中的狀況並無出現,而是在HashMap同時存在了value="findingsealy"value="henrylin"的兩個鍵值對,而且它們的key值仍是不相同的,這顯然是錯誤的。code

  2. 在獲取value值時,咱們分別用三個Person對象去查找,這三個對象和咱們剛剛存入的三個key值(在指望中)是相同的,可是查找出的倒是三個null值,這顯然也是錯誤的。對象

那麼,正確的方法其實在不少地方都是被描述過了,直接對Person類進行修改,重載equalshashCode方法,修改事後的Person類以下:字符串

javapublic class Person {

    private String id;

    public Person(String id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (id != null ? !id.equals(person.id) : person.id != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }
}

那麼,當咱們從新執行上述的檢驗程序時,獲得的結果以下:get

{Person@ba31=findingsea, Person@ba32=linyin, Person@ba33=findingsealy}
findingsea
linyin
findingsealy

能夠看到,以前指出的亮點錯誤都獲得了改正。那麼,爲何會這樣呢?hash

HashMap中,查找key的比較順序爲:class

  1. 計算對象的Hash Code,看在表中是否存在。
  2. 檢查對應Hash Code位置中的對象和當前對象是否相等。

顯然,第一步就是要用到hashCode()方法,而第二步就是要用到equals()方法。在沒有進行重載時,在這兩步會默認調用Object類的這兩個方法,而在Object中,Hash Code的計算方法是根據對象的地址進行計算的,那兩個Person("003")的對象地址是不一樣的,因此它們的Hash Code也不一樣,天然HashMap也不會把它們當成是同一個key了。同時,在Object默認的equals()中,也是根據對象的地址進行比較,天然一個Person("003")和另外一個Person("003")是不相等的。

理解了這一點,就很容易搞清楚爲何須要同時重載hashCode()equals兩個方法了。

  • 重載hashCode()是爲了對同一個key,能獲得相同的Hash Code,這樣HashMap就能夠定位到咱們指定的key上。
  • 重載equals()是爲了向HashMap代表當前對象和key上所保存的對象是相等的,這樣咱們才真正地得到了這個key所對應的這個鍵值對。

還有一個細節,在Person類中對於hashCode()的重在方法爲:

java@Override
public int hashCode() {
    return id != null ? id.hashCode() : 0;
}

這裏可能有疑惑的點在於:爲何能夠用String類型的變量的Hash Code做爲Person類的Hash Code值呢?這樣new Person(new String("003"))new Person(new String("003"))Hash Code是相等的嗎?

來看看如下代碼的輸出:

javaSystem.out.println("findingsea".hashCode());
System.out.println("findingsea".hashCode());
System.out.println(new String("findingsea").hashCode());
System.out.println(new String("findingsea").hashCode());
728795174
728795174
728795174
728795174

能夠看到四條語句的輸出都是相等的,很直觀的合理的猜想就是String類型也重載了hashCode()以根據字符串的內容來返回Hash Code值,因此相同內容的字符串具備相同的Hash Code

同時,這也說明了一個問題:爲何在已知hashCode()相等的狀況下,還須要用equals()進行比較呢?就是由於避免出現上述例子中的出現的狀況,由於根據對Person類的hashCode()方法的重載實現,Person類會直接用id這個String類型成員的Hash Code值做爲本身的Hash Code值,可是很顯然的,一個Person("003")和一個String("003")是不相等的,因此在hashCode()相等的狀況下,還須要用equals()進行比較。

如下例子能夠做爲上述說明的佐證:

javaSystem.out.println(new Person("003").hashCode()); // 47667
System.out.println(new String("003").hashCode()); // 47667

System.out.println(new Person("003").equals(new String("003"))); // false

以上便是所有。

相關文章
相關標籤/搜索