2011-07-20 62 views
9

我只是想知道,如果HashMap的關鍵是可變的,會發生什麼,下面的測試程序證明,我無法理解當兩個平等的,hashCode方法返回 真實值相同,爲什麼hashmap.containsKey返回false更新Java的HashMap的關鍵

public class MutableKeyHashMap { 

    public static void main(String []a){ 

      HashMap<Mutable, String> map = new HashMap<Mutable, String>(); 
      Mutable m1 = new Mutable(5); 
      map.put(m1, "m1"); 
      Mutable m2 = new Mutable(5); 
      System.out.println(map.containsKey(m2));  

      m2.setA(6); 
      m1.setA(6); 
      Mutable m3 = map.keySet().iterator().next(); 

      System.out.println(map.containsKey(m2)+" "+m3.hashCode()+"  "+m2.hashCode()+"  "+m3.equals(m2)); 

    } 
} 
class Mutable { 

    int a; 

    public Mutable(int a) { 

     this.a = a; 
    } 

    @Override 
    public boolean equals(Object obj) { 

     Mutable m = (Mutable) obj; 
     return m.a == this.a ? true : false; 
    } 

    @Override 
    public int hashCode(){ 
     return a; 
    } 

    public void setA(int a) { 

     this.a = a; 
    } 

    public int getA() { 
     return a; 
    } 
} 

此輸出:

真 假6 6真正

+0

你可能會發現這篇關於Python的方法很有趣:http://pyfaq.infogami.com/why-must-dictionary-keys-be-immutable – jtoberon

回答

14

javadoc解釋它

注:如果可變對象是巨大的,一定要小心用作地圖鍵。如果對象的值以影響等於比較的方式更改,而對象是地圖中的關鍵字,則不會指定地圖的行爲。

基本上,在地圖不使用可變對象作爲鍵,你會被燙傷

浮想聯翩,因爲文檔可能不會出現明確的,我相信這裏的相關問題是`以影響等於的方式改變「,並且您似乎認爲每次包含被調用時都會調用equals(Object)。文檔沒有說,措辭意味着他們可能被允許緩存計算。

看看source,似乎是因爲你的hashCode返回一個不同的值(是5,現在是6),它可能是根據實現細節在不同的桶中查找的。

+0

評論我自己的答案:在我看來,鏈接實現不履行文檔的義務,因爲問題類型的可變性會影響hashCode而不是equals。 – ptomli

+0

是的,但根據Object定義的契約,合法的hashCode實現將導致功能上等效的行爲。 – Affe

+0

不完全相同:「只要修改了對象的等值比較中未使用的信息,hashCode方法就必須始終返回相同的整數」。由於用於equals的數據已經改變,所以hashCode也可以改變它。 Map文檔意味着equals是平等的仲裁者,而實現使用hashCode錯誤地查找條目,據我所知。這裏唯一的救星就是Map文檔並不完全清楚實現如何在生命週期中使用equals。我不知道鏈接源的來源。 – ptomli

6

HashMap將您的對象置於散列鍵5的位置。然後您將密鑰更改爲6並使用containsKey詢問地圖是否包含該對象。地圖看起來位置6並且什麼都沒發現,所以它回答了false

那麼不要那樣做,那麼。

+0

這對我來說很有意義。我遵循你所說的話,然後在'm1.setA(6);'行後面加上'map.put(m1,「m1」);''。它導致「真實的真實6 6真實」。 – smslce

+0

您應該在更改密鑰之前將其從哈希映射中移除,否則您會創建內存泄漏。 – starblue

9

你可以想想如果這樣,Map有16個桶。當你給它一個A == 5的對象時,它將它扔到bucket 5中。現在你可以把A改成6,但它仍然在bucket 5中。Map不知道你改變了A,它不會重新排列東西內部。

現在你用A == 6過來了另一個對象,並且你問地圖是否有其中之一。它看起來在第6桶,並說:「不,沒有。」這不會去檢查所有其他桶爲你。

很明顯,事情如何進入桶更復雜,但這就是它的核心。

0

當您第一次放置「m1」時,hashCode()爲5.因此,HashMap使用5將值放入相應的存儲桶中。在更改m2後,hashCode()爲6,因此當您嘗試查找所投入的值時,它所查看的桶是不同的。

+0

除非地圖調整大小發生在你突變的時間和你試圖再次查找的時間之間。當重新調整大小時,重新計算hashCodes。 –

0

ptomli答案的代碼示例。

import java.util.*; 

class Elem { 
    private int n; 

    public Elem(int n) { 
     this.n = n; 
    } 

    public void setN(int n) { 
     this.n = n; 
    } 

    @Override 
    public int hashCode() { 
     return n; 
    } 

    @Override 
    public boolean equals(Object e) { 
     if (this == e) 
      return true; 
     if (!(e instanceof Elem)) 
      return false; 
     Elem an = (Elem) e; 
     return n == an.n; 
    } 
} 

public class MapTest { 
    public static void main (String [] args) { 
     Elem e1 = new Elem(1); 
     Elem e2 = new Elem(2); 

     HashMap map = new HashMap(); 
     map.put(e1, 100); 
     map.put(e2, 200); 

     System.out.println("before modification: " + map.get(e1)); 
     e1.setN(9); 
     System.out.println("after modification using updated key: " + map.get(e1)); 

     Elem e3 = new Elem(1); 
     System.out.println("after modification using key which equals to the original key: " + map.get(e3));  
    } 
} 

編譯並運行它。結果是:

before modification: 100 
after modification using updated key: null 
after modification using key which equals to the original key: null 

我在Linux上使用Java 6。