Java 为什么在HashMap中使用键检索这些值?
在HashMap上运行以下代码时,第二行输出为“Line2:null”,这是一个令人费解的问题:Java 为什么在HashMap中使用键检索这些值?,java,hashmap,Java,Hashmap,在HashMap上运行以下代码时,第二行输出为“Line2:null”,这是一个令人费解的问题: import java.util.*; class Dog { public Dog(String n) {name = n;} public String name; public boolean equals(Object o) { if((o instanceof Dog) && (((Dog)o).name == name)) { ret
import java.util.*;
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 HelloWorld{
public static void main(String []args){
Map<Object, Object> m = new HashMap<Object, Object>();
Dog d1 = new Dog("clover");
m.put(d1, "Dog key");
System.out.println("Line1: " + m.get(d1));
d1.name = "magnolia";
System.out.println("Line2: " + m.get(d1));
d1.name = "clover";
System.out.println("Line3: " + m.get(new Dog("clover")));
d1.name = "arthur";
System.out.println("Line4: " + m.get(new Dog("clover")));
}
}
import java.util.*;
班犬{
公共狗(字符串n){name=n;}
公共字符串名称;
公共布尔等于(对象o){
if((狗的实例)和((狗的)o.name==name)){
返回true;
}
else{return false;}
}
public int hashCode(){return name.length();}
}
公共类HelloWorld{
公共静态void main(字符串[]args){
Map m=新的HashMap();
狗d1=新狗(“三叶草”);
m、 put(d1,“狗钥匙”);
System.out.println(“第1行:+m.get(d1));
d1.name=“木兰”;
System.out.println(“第2行:+m.get(d1));
d1.name=“三叶草”;
System.out.println(“第3行:+m.get(新狗(“三叶草”)));
d1.name=“亚瑟”;
System.out.println(“第4行:+m.get(新狗(“三叶草”)));
}
}
显示的输出为:
第1行:狗钥匙
第2行:空
第3行:狗钥匙
第4行:空
是的,我确实意识到修改实例变量name反过来会影响Dog实例的hashcode,因为我计算hashcode的方式不同。但是,我使用的是与密钥相同的实例!那么,为什么get()方法找不到相应的值呢?似乎一旦一对被推入HashMap,密钥就永远用值进行硬编码!这就是它的工作原理吗?也就是说,在将哈希代码对放入HashMap之前,一旦确定了值的哈希代码,哈希代码就再也不能修改了
是的,我意识到修改实例变量名反过来会影响Dog实例的hashcode,因为我计算hashcode的方式。但是,我使用的是与密钥相同的实例!那么,为什么get()方法找不到相应的值呢
这个解释有点过于简单,但它仍然应该说明这里发生了什么。将HashMap视为键值对数组。hashCode值用于决定在哪个索引处获取/放置给定值
例如,如果哈希代码返回7,那么它将尝试获取/放置数组中索引7处的值。假设您正在执行一个put
操作,但是索引7已经满了。有几种方法可以解决这个问题,但最简单的方法是在每个数组索引处有一个具有相同散列的值的bucket(例如,一个链表),然后将新值添加到bucket中
现在假设您正在执行一个get
操作。检查数组中与哈希值对应的索引,但不能保证这就是您要查找的值(因为可能存在哈希冲突)。您需要确保该位置的键也与用于查找的键相同。如果钥匙不相等,那么你继续(在桶里)寻找。如果没有地方可看(即,您已搜索了整个桶),则该值不在地图中
这就是你的代码被破坏的地方。您正在查找正确的bucket(因为密钥的散列与原始散列相同),但是equals
方法现在在执行“深度”比较以检查您是否实际拥有正确的密钥/值对时返回false
这就是它的工作原理吗?也就是说,在将哈希代码对放入HashMap之前,一旦确定了值的哈希代码,哈希代码就再也不能修改了
当您了解如何使用数组(如上所述)实现HashMap时,很明显这实际上是预期的行为,而改变键是一个非常糟糕的主意
一些旁注… 您也应该使用
equals
来比较名称:
((Dog)o).name.equals(name))
以下是您当前拥有的:
((Dog)o).name == name)
检查名称
字符串是否为同一实例,而不是字符串是否具有相同的值
只要返回逻辑运算的结果,就可以大大简化equals
方法:
public boolean equals(Object o) {
return (o instanceof Dog) && (((Dog)o).name.equals(name));
}
当然可以。如果您尝试使用修改过的hashcode获取(),那么HashMap应该如何知道要匹配什么?您应该使用
等于而不是=
来比较字符串(例如Dog.name
)。此外,您还期望与当前的hashCode
实现发生大量冲突。您可以使用返回name.hashCode()
代替。Oliver是正确的。。重写的hashcode
方法返回基于狗名字段的值。由于clover
返回6,而magnolia
返回8,当您调用get()时,将使用作为键传递的对象的hashcode,因为前一个条目的hashcode是6,与您第二次传递的hashcode不同(8),它将无法找到相应的条目并返回nullYeah,但因为它们使用的是字符串文字,这与问题涉及的行为无关。@Radiodef-啊,说得好。我刚才看到了比较字符串的==
,然后得出了一个错误的结论。谢谢。我将回顾我以前读过的桶概念。关于==,幸运的是它在这里工作,因为我使用的是字符串文本。最确切地说,正确的方法是将==替换为equals()。