4

我在一个类(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() 方法中像 clover!=arthur 一样失败,但它成功了!!我注意到,当 hashCode 成功(即 lengh==6)时,即使 equals() 方法失败,也会检索存储在地图中的值,我更改了 == 并改用了 equals() 但没有发生任何更改,问题仍然存在.

4

3 回答 3

5

您想使用 .equals() 而不是 == 比较字符串,它比较引用。

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

同样,equals 方法也有点偏离。如果名称为空怎么办?你会得到一个空指针异常。您需要为该特殊情况添加另一个检查。

于 2012-08-01T22:48:29.470 回答
3

为什么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);

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


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

于 2012-08-01T23:08:34.367 回答
1

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

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

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

于 2012-08-01T23:00:20.417 回答