有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

奇怪的Java HashMap行为找不到匹配的对象

我在试图在java.util.HashMap中找到一个密钥时遇到了一些奇怪的行为,我想我遗漏了一些东西。代码段基本上是:

HashMap<Key, Value> data = ...
Key k1 = ...

Value v = data.get(k1);
boolean bool1 = data.containsKey(k1);
for (Key k2 : data.keySet()) {
    boolean bool2 = k1.equals(k2);
    boolean bool3 = k2.equals(k1);
    boolean bool4 = k1.hashCode() == k2.hashCode();
    break;
}

这种奇怪的for循环是存在的,因为对于特定的执行我碰巧知道data此时只包含一个项,它是k1,实际上bool2bool3bool4在该执行中将被计算为true^但是,{}将被计算为false,并且v将为空

现在,这是一个更大程序的一部分-我无法在更小的样本上重现错误-但在我看来,无论程序的其余部分做什么,这种行为永远不会发生

编辑:我已经手动验证了哈希代码在对象插入到映射和查询对象之间没有变化。我会继续检查这个场地,但还有其他选择吗


共 (6) 个答案

  1. # 1 楼答案

    从映射接口的API描述中:

    Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map. A special case of this prohibition is that it is not permissible for a map to contain itself as a key. While it is permissible for a map to contain itself as a value, extreme caution is advised: the equals and hashCode methods are no longer well defined on such a map.

    此外,对于用作映射键的类型,equals()和hashCode()的行为有非常具体的要求。不遵守此处的规则将导致各种未定义的行为

  2. # 3 楼答案

    这个应用程序是多线程的吗?如果是这样,另一个线程可以在data.containsKey(k1)调用和data.keySet()调用之间更改数据

  3. # 4 楼答案

    也许关键类看起来像

    Key
    {
        boolean equals = false ;
    
        public boolean equals ( Object oth )
        {
             try
             {
                  return ( equals ) ;
             }
             finally
             {
                  equals = true ;
             }
        }
    }
    
  4. # 5 楼答案

    如果您确定哈希代码在插入密钥和执行包含检查之间没有变化,那么某个地方就出了严重的问题。您确定使用的是java.util.HashMap而不是某种类型的子类吗?您知道您正在使用的JVM实现是什么吗

    以下是Sun 1.6.0_20 JVM中java.util.HashMap.getEntry(Object key)的源代码:

    final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    

    如您所见,它检索hashCode,转到表中相应的插槽,然后对该插槽中的每个元素执行equals检查。如果这是您正在运行的代码,并且密钥的哈希代码没有更改,那么它必须执行等于检查,该检查必须失败

    下一步是为我们提供更多的代码或上下文—至少是密钥类的hashCodeequals方法

    或者,如果可以的话,我建议连接到调试器。观察您的密钥散列到哪个存储桶,并通过containsKey检查查看失败的地方

  5. # 6 楼答案

    如果密钥的哈希代码在插入到映射后发生更改,则可能发生此行为

    以下是您描述的行为示例:

    public class Key
    {
    int hashCode = 0;
    
    @Override
    public int hashCode() {
        return hashCode;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Key other = (Key) obj;
        return hashCode == other.hashCode;
    }
    
    public static void main(String[] args) throws Exception {
        HashMap<Key, Integer> data = new HashMap<Key, Integer>();
        Key k1 = new Key();
        data.put(k1, 1);
    
        k1.hashCode = 1;
    
        boolean bool1 = data.containsKey(k1);
        for (Key k2 : data.keySet()) {
            boolean bool2 = k1.equals(k2);
            boolean bool3 = k2.equals(k1);
            boolean bool4 = k1.hashCode() == k2.hashCode();
    
            System.out.println("bool1: " + bool1);
            System.out.println("bool2: " + bool2);
            System.out.println("bool3: " + bool3);
            System.out.println("bool4: " + bool4);
    
            break;
        }
    }
    }