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

时间:2010-07-28 20:35:54

标签: 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,而且确实{ {1}},bool2bool3将在该执行中评估为bool4。但是,true将评估为bool1false将为空。

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

编辑:我已手动验证哈希码在对象插入地图的时间和查询时间之间会发生变化。我会继续检查这个场地,但还有其他选择吗?

6 个答案:

答案 0 :(得分:9)

如果在将密钥插入映射后更改密钥的哈希码,则会发生此行为。

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

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;
    }
}
}

答案 1 :(得分:3)

从Map界面的API描述:

  

注意:如果,必须非常小心   可变对象用作映射键。   未指定地图的行为   如果对象的值发生了变化   以一种影响平等的方式   比较,而对象是一个关键   在地图上。这是一个特例   禁止是它不是   允许地图包含   本身作为关键。虽然它是   允许地图包含   作为一种价值,极端谨慎   建议:equals和hashCode   方法不再明确定义   这样的地图。

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

答案 2 :(得分:1)

如果您确定哈希码在插入密钥和执行包含检查的时间之间没有变化,那么某处就会出现严重错误。你确定你使用的是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检查。如果这是你正在运行的代码并且密钥的哈希码没有改变,那么它必须进行必须失败的等于检查。

下一步是为您提供更多代码或上下文 - 至少为您的Key类的hashCodeequals方法。

或者,如果可以的话,我建议连接到调试器。观察您的密钥被散列到哪个存储桶,并逐步执行containsKey检查以查看它的失败位置。

答案 3 :(得分:0)

这个应用程序是多线程的吗?如果是这样,另一个主题可能会更改data.containsKey(k1)来电和data.keySet()来电之间的数据。

答案 4 :(得分:0)

如果equals()为两个对象返回true,则hashCode()应该返回相同的值。如果equals()返回false,则hashCode()应返回不同的值。 供参考:

http://www.ibm.com/developerworks/java/library/j-jtp05273.html

答案 5 :(得分:0)

也许Key类看起来像

Key
{
    boolean equals = false ;

    public boolean equals ( Object oth )
    {
         try
         {
              return ( equals ) ;
         }
         finally
         {
              equals = true ;
         }
    }
}