【问题标题】:Updating Java HashMap key更新 Java HashMap 键
【发布时间】:2011-10-09 16:32:49
【问题描述】:

我只是想知道,如果 HashMap 的键是可变的会发生什么,下面的测试程序证明了这一点,我无法理解 equals 和 hashCode 方法何时返回 true 和相同的值,为什么hashmap.containsKey 返回false

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 真

【问题讨论】:

标签: java hashmap equals hashcode


【解决方案1】:

javadoc 解释它

注意:如果将可变对象用作映射键,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是映射中的键,则不会指定映射的行为。

基本上,不要在 Map 中使用可变对象作为键,你会被烧毁

推断,因为文档可能看起来不清楚,我相信这里的相关点是“以影响 equals 的方式进行更改”,并且您似乎假设每次调用 contains 时都会调用 equals(Object)。文档没有这么说,措辞暗示它们可能被允许缓存计算。

查看source,似乎因为您的 hashCode 返回了一个不同的值(以前是 5,现在是 6),可能是根据实现细节在不同的存储桶中查找它。

【讨论】:

  • 评论我自己的答案:在我看来,链接的实现没有履行文档的义务,因为所讨论类型的可变性会影响 hashCode 而不是 equals。
  • 是的,但是根据 Object 定义的合同合法的 hashCode 实现将导致功能上等效的行为。
  • 不完全:“hashCode 方法必须始终返回相同的整数,前提是没有修改对象上的 equals 比较中使用的信息”。由于用于 equals 的数据已更改,因此 hashCode 也可以更改。 Map 文档暗示 equals 是相等的仲裁者,而据我所知,实现错误地使用 hashCode 来查找条目。这里唯一的救星是 Map 文档并不清楚实现如何在其生命周期中使用 equals。而且我不知道链接来源的出处。
  • 所有文档都说如果equals的计算发生变化,地图可能会被破坏。由于 hashCode 不允许在没有影响 equals 的更改的情况下进行更改,因此任何破坏此映射实现的更改都是影响 equals 的更改,因此文档涵盖了它。
  • @Affe 是的,同意。 (之前).equals(X) == (之后).equals(X) 而(之前).hasCode() != (之后).hashCode()。 Map 的要求比 Object.equals/Object.hashCode 的要求更严格。但是,这与您之前的评论不太吻合,re: Object.hashCode,Map 需要的 equals/hashCode 比 Object 更多,文档只需要一点消化。
【解决方案2】:

你可以这样想,如果 Map 有 16 个桶。当你给它一个 A == 5 的对象时,它会将它扔到存储桶 5 中。现在你可以将 A 更改为 6,但它仍然在存储桶 5 中。地图不知道你更改了 A,它不会重新排列东西内部。

现在你带着另一个 A == 6 的对象过来,你问地图是否有其中一个。它去查看 6 号桶,然后说“不,那里什么都没有”。它不会为您检查所有其他存储桶。

显然,如何将事物放入桶中比这更复杂,但这就是它的核心工作方式。

【讨论】:

    【解决方案3】:

    HashMap 将您的对象放在哈希键 5 的位置。然后你把key改成6,用containsKey询问map是否包含对象。地图查看位置 6 并没有找到任何东西,所以它回答 false

    那么,不要那样做。

    【讨论】:

    • 这对我来说很有意义。我按照你所说的然后在 m1.setA(6); 行之后添加了 map.put(m1, "m1"); 。结果是“true true 6 6 true”。
    • 您应该在更改密钥之前将其从哈希映射中删除,否则会造成内存泄漏。
    【解决方案4】:

    当您第一次输入“m1”时,hashCode() 是 5。因此,HashMap 使用 5 将值放入适当的存储桶中。更改 m2 后,hashCode() 为 6,因此当您尝试查找放入的值时,它查找的存储桶不同。

    【讨论】:

    • 除非地图调整大小发生在您变异的时间和您再次尝试查找它的时间之间。当调整大小发生时,hashCodes 会重新计算。
    【解决方案5】:

    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。

    【讨论】:

      猜你喜欢
      • 2012-11-16
      • 1970-01-01
      • 1970-01-01
      • 2021-04-11
      • 1970-01-01
      • 2016-02-07
      • 1970-01-01
      • 2011-03-21
      • 1970-01-01
      相关资源
      最近更新 更多