Java 重写hashCode()和equals()以在hashMap中存储一个工作不正常的对象
我重写了类(Dog)中的hashCode()和equals(),以便从hashMap中存储和检索它的实例,代码如下:Java 重写hashCode()和equals()以在hashMap中存储一个工作不正常的对象,java,hashmap,scjp,Java,Hashmap,Scjp,我重写了类(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)) {
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();
}
}
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());
}
}
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();
}
}
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());
}
}
公共类映射测试{
公共静态void main(字符串[]args){
Map m=新的HashMap();
m、 put(“k1”,新狗(“爱子”);
狗d1=新狗(“三叶草”);
m、 放置(d1,“狗钥匙”);/#1
System.out.println(m.get(“k1”);
字符串k2=“k2”;
d1.name=“亚瑟”/#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()而不是==,来比较字符串,后者比较引用
public boolean equals(Object o) {
if ((o instanceof Dog)
&& (((Dog) o).name.equals(name))) {
return true;
} else {
return false;
}
}
另外,equals方法有点不合适。如果名称为空怎么办?您将得到一个空指针异常。您需要为该特殊情况添加另一个检查。除了将字符串与
==
进行比较的问题之外,所发生的是您更改了狗的名称
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中查找,当然,它会在那里找到狗
如果你把名字改成一个不同长度的名字,它(通常)会在不同的桶里找不到狗。为什么等于永不失败?
根据Tom的评论:
。。如果修改贴图中的对象但保持键不变,则仍然可以使用相同的键实例获取值dog.equals(dog)在代码中始终为真(除非有并发修改)
也就是说,行:
d1.name = "arthur";
正在对HashMap中已存在的对象进行变异。比较(其中t
“打印”为真或假):
因此,等于
永远不会失败,因为它将对象与自身进行比较:)
一开始我也没注意到:记住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。执行查找(或设置)时,首先通过哈希代码查找存储桶,然后使用equals
检查已映射到存储桶的每个键。如果bucket中没有键,则永远不会调用equals
这解释了为什么在原始post中将名称
更改为长度为8的字符串会导致查找失败:最初选择不同的bucket(例如,一个为空的bucket),因此不会对其他bucket中存在的现有密钥调用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。但是,以上是成功查找的唯一保证
当然,有关比较字符串的正确方法,请参阅其他回复。我真的希望Java编译器在x==y
上发出警告/错误,其中x/y的类型不是原语。(顺便说一句,对于各种形式,它确实在C#/VS中发出警告。)也就是说,我真的希望需要一个显式的(object)x==(object)y
形式(或其他形式)。。这个问题提得很好。不完全遵循你正在做的事情。如果修改贴图中的对象但保持键不变,则仍然可以使用相同的键实例获取值dog.equals(dog)
在你的代码中总是正确的(除非同时修改)。@TomHawtin-tackline请作为答案发布。。我错过了这一点,其他两张海报也错过了。)@我感到困惑。“你发帖子,我会向上投票”。@TomHawtin tackline在那里,评论为你引用帖子。没有任何变化,它仍然返回Dog键。这似乎与问题无关——首先,所有字符串都被保留。这是一个错误的假设,因为他的源代码只是示例代码。从数据库读入字符串值的那一刻,=
即被中断equals
是比较字符串的正确方法。@SteveKuo字符串文字由要插入的语言规范保证。是的,您可以更改源,它可以做一些不同的事情,但问题是关于这个确切源的行为。@SteveKuo字符串文字是。在JLS中应该不太难找到。这不是真的。嗯,是的。但它并不完整。OP解释了长度和铲斗匹配。这仍然不能解释“equals not failing”。当然,它将查看同一个hashBucket,这是第一步,然后equals()方法将在第二步确定这两个是否相等,实际上它们不是t@Eslam但它们是相等的(这就是为什么equals
总是返回true),请参见Tom在主帖子中的评论。@Eslamd1
是地图中的键,您查找的对象是同一个对象。如果您的答案是正确的,那么当我传递一个大于6个字符(即d1.name=“Magnolia”)的名称时,为什么返回空值?@Eslam Per没有按照帖子找到正确的桶hashCode()
用于首先查找bucket,而equals仅用于在bucket中找到的对象。(这就是为什么hashCode
应该是稳定的。)@Eslam EachHa