Java 为什么在HashMap中使用键检索这些值?

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

在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)) {
        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()。