我在一个类(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中查找,当然它会在那里找到狗。
如果你把名字改成一个不同长度的名字,它(通常)会在不同的桶里寻找,然后找不到狗。