重写hashCode()和equals()以将对象存储在hashMap中,但不能正常工作



我在一个类(Dog)中重写了hashCode()和equals(),以便从hashMap中存储和检索它的实例,代码如下:

class Dog {
public Dog(String n) {
    name = n;
}
public String name;
public boolean equals(Object o) {
    if ((o instanceof Dog)
            && (((Dog) o).name == name)) {
       return true;
    } else {
       return false;
    }
}
public int hashCode() {
    return name.length();
   }
}

hashMap代码如下:

public class MapTest {
public static void main(String[] args) {
    Map<Object, Object> m = new HashMap<Object, Object>();
    m.put("k1", new Dog("aiko"));
    Dog d1 = new Dog("clover");
    m.put(d1, "Dog key");    // #1
    System.out.println(m.get("k1"));
    String k2 = "k2";
    d1.name = "arthur";     // #2
    System.out.println(m.get(d1)); #3 
    System.out.println(m.size()); 
  }
}

问题是,在2时,我将存储在hashMap中的dog对象的名称更改为1,3时的预期输出为NULL,但实际输出为dog-Key!!我预计它会像三叶草一样在equals()方法中失败=亚瑟,但它成功了!!我注意到,当hashCode成功(即lengh==6)时,即使equals()方法失败,也会检索存储在映射中的值,我更改了==并使用了equals(,但没有发生任何更改,问题仍然存在。

您想要使用.equals()not==来比较字符串,它比较引用。

public boolean equals(Object o) {
    if ((o instanceof Dog)
            && (((Dog) o).name.equals(name))) {
       return true;
    } else {
       return false;
    }
}

同样,equals方法也有点错误。如果name为null怎么办?您将得到一个空指针异常。您需要为该特殊情况添加另一张支票。

为什么equals"从不失败"

根据Tom的评论:

如果修改贴图中的对象,但保持关键点不变,则仍然可以使用相同的关键点实例获取值dog.equals(dog)在您的代码中始终为true(除非同时修改)

也就是说,这条线:

d1.name = "arthur"; 

正在更改HashMap中已有的对象。与(其中t"打印"true或false)进行比较:

Dog d1 = new Dog("clover");
// what "put" is effectively doing: there is *no* Copy/Clone
Dog inMap = d1;
t(inMap == d1);                 // true: same object, reference equality!
d1.name = "arthur";
t(inMap.name.equals("arthur")); // true: same object! "name" member was *mutated*
t(d1.equals(inMap));            // true: same object!

因此equals永远不会失败,因为它正在将对象与自身进行比较:)

一开始我也错过了:记住Java有对象共享调用语义。也就是说,对于传递给方法的对象,没有隐式复制/克隆/复制

那么,如何让它失败:

Dog d1 = new Dog("clover");
Dog d2 = new Dog("clover");
t(d1 == d2);                   // false: different objects!
m.put(d1, "Dog key");          // put in with D1 object
System.out.println(m.get(d1)); // "Dog key"   -okay, equals
System.out.println(m.get(d2)); // "Dog key"   -okay, equals
d2.name = "arthur";            // *mutate* D2 object
t(d1.equals(d2));              // false: no longer equal
System.out.println(m.get(d1)); // "Dog key"   -okay, always equals, as per above
System.out.println(m.get(d2)); // ""          -no good, no longer equals

hashCode是如何适应的

散列码用于确定要放入密钥(和值对)的散列表bucket。当执行查找(或设置)时,bucket是首先通过散列码查找,然后用equals检查已映射到bucket的每个密钥。如果bucket中没有键,则永远不会调用equals。

这解释了为什么在原始帖子中将name更改为长度为8的String会导致查找失败:最初选择了一个不同的存储桶(例如,一个为空的存储桶),因此equals永远不会对其他存储桶中存在的现有密钥调用。同一个对象键可能已经存在,但它从未被查看过!

那么,如何使用不同的哈希代码使其失败:

Dog d1 = new Dog("clover");
m.put(d1, "Dog key");          // put in with D1 object, hashCode = 6
System.out.println(m.get(d1)); // "Dog key" -okay, hashCode = 6, equals
d1.name = "Magnolia";          // change value such that it changes hash code
System.out.println(m.get(d1)); // ""        -fail, hashCode = 8, equals
// ^-- attaching a debugger will show d1.equals is not called

因此,对于要在哈希表(如HashMap)中找到的密钥,它必须是这样的:

k.hashCode() == inMap.hashCode() && k.equals(inMap);

可能有许多哈希代码映射到同一个bucket。然而,以上是查找成功的唯一保证。


当然,有关比较字符串的正确方法,请参阅其他回复。

除了将字符串与==进行比较的问题外,还需要更改狗的名称

Dog d1 = new Dog("clover");
m.put(d1, "Dog key");    // #1
System.out.println(m.get("k1"));
String k2 = "k2";
d1.name = "arthur";     // #2

变为具有相同长度的。因此,在地图中的查找在同一个hashbucket中查找,当然它会在那里找到狗。

如果你把名字改成一个不同长度的名字,它(通常)会在不同的桶里寻找,然后找不到狗。

最新更新