繁体   English   中英

覆盖hashCode()和equals()以存储hashMap中的对象无法正常工作

[英]override hashCode() and equals() to store an object inside hashMap not working properly

我在类(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内的狗对象的名称为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;
    }
}

另外,等于方法有点偏。 如果name为null怎么办? 你会得到一个空指针异常。 您需要为该特殊情况添加另一个检查。

为什么equals “永不失败”?

根据汤姆的评论:

..如果您修改地图中的对象但保持密钥不变,那么您仍然可以使用相同的密钥实例获取值。 dog.equals(dog)将始终与您的代码一致(除非有并发修改)

就是这条线:

d1.name = "arthur"; 

正在改变 HashMap中的对象。 比较(其中t “打印”为真或假):

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具有Call By Object-Sharing语义。 也就是说,传递给方法的对象没有隐式的复制/克隆/复制

那么,如何让它失败:

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如何适应?

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

这解释了为什么在原始帖子中将name更改为长度为8的字符串会导致查找失败:最初选择了一个不同的存储桶(例如,一个为空的存储桶),因此永远不会在存在其中的现有密钥上调用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);

可能有许多哈希码映射到同一个桶。 然而上述的唯一保证查找成功。


当然,请参阅其他回复,了解一般比较字符串的正确方法。

除了将字符串与==进行比较的问题之外,发生的事情是您更改了狗的名字

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中,当然它在那里找到了狗。

如果您将名称更改为具有不同长度的名称,它将(通常)查看不同的桶,然后找不到狗。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM