2011-07-20 29 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。