import java.util.*;
class Dog {
public Dog(String n) { name = n; }
public String name;
public boolean equals(Object o) {
System.out.println("equals called..");
if((o instanceof Dog) &&
(((Dog)o).name == name)) {
return true;
} else {
return false;
}
}
public int hashCode() {
System.out.println("hashCodecalled..");
return name.length();
}
}
class SampleClass {
public static void main(String[] args) {
Map<Object, String> m = new HashMap<Object, String>();
Dog d1 = new Dog("clover");
m.put(d1, "Dog key");
d1.name ="arthur";
System.out.println(m.get(d1));
}
}
在上面的代码中,我得到的输出是:
hashCodecalled..
hashCodecalled..
Dog key
在我做d1.name =“arthur”之后
我期待输出
hashCodecalled..``
hashCodecalled..
equals called..
null
答案 0 :(得分:6)
由于HashMap
具有您正在寻找的确切对象,因此无需调用equals
来验证该对象是否正确。
当get
来自HashMap
的对象时,首先评估hashCode
以找到正确的存储桶。然后,搜索存储桶。但是首先使用==
比较存储桶中的每个密钥。如果对象不是正在寻找的对象,则使用equals
。
在Java 7中,get
HashMap
方法中代码的关键部分是
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
答案 1 :(得分:2)
This question has been asked before in here.
根据hashCode方法,它表示&#34;如果两个对象根据equals(Object)方法相等,则在两个对象中的每一个上调用hashCode方法必须产生相同的整数结果。&#34 ;通常,每当您创建equals方法时,请确保它与返回的哈希码值保持一致。
HashMap将始终在equal方法之前使用hashCode比较对象。对于您的情况,如果您希望看到要调用的相等方法,则可以通过仅将哈希码值设为常量来模拟哈希冲突场景。 (警告:这不是一个糟糕的例子,因为它在性能方面影响很大。)
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
Map<Object, String> m = new HashMap<Object, String>();
Dog d1 = new Dog("clover");
Dog d2 = new Dog("clover 2");
m.put(d1, "Dog 1");
m.put(d2, "Dog 2");
System.out.println(m.get(d1));
}
class Dog {
private String name;
public Dog(String n) {
name = n;
}
@Override
public boolean equals(final Object o) {
System.out.println("equals called..");
return true;
}
@Override
public int hashCode() {
System.out.println("hashCodecalled..");
return 1; // constant
}}
结果:
hashCodecalled ..
hashCodecalled ..
等于叫..
hashCodecalled ..
狗2
最后,当然你需要插入至少两个对象才能使它有效。
答案 2 :(得分:1)
请参阅java.util.HashMap.get方法代码。
首先检查键== e.key然后检查key.equals(e.key)。
在您的代码中,您将相同的实例传递给get方法。 所以关键== e.key。和key.equals(e.key)将不会被执行。
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
答案 3 :(得分:1)
从HashMap的代码中可以看出,它首先检查null,因为hashcode没有改变(name的长度没有改变),然后==被比较(如David所说)。这是真的,等于()不会检查,所以它会返回那个狗。
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
答案 4 :(得分:1)
在HashMap中,对象存储在称为桶(密钥)的组中。具有相同hashCode值的所有关键对象都存储在一个存储桶中。因此,例如Dog k1 = new Dog("clover")
,Dog k2 = new Dog("clever")
和Dog k3 = new Dog("123456");
都存储在一个存储桶中,因为hashCode()
Dog
方法返回的名称长度为hashCode值。这里所有3个Dog对象的名称长度为6,因此进入一个桶中。
当调用get()
方法时,HashMap
使用给定键的相同hashCode定位存储桶,如果找到,则在存储桶中的每个对象上首先使用simple ==相等检查来查看如果给定的桶中的密钥和对象是相同的对象,如果没有,则equals()
方法相等性检查以找到确切的对象。
因此,如果在使用键(Dog对象)将键值对存储(放入)HashMap后更改了键对象内容,则可能无法查找/检索原始对象(有时可能)。让我们看看如何。
例如,如果Dog k3
name
更改为“1234”,则hashCode现在变为4.假设只有k1,k2和k3被放入HashMap,{{1}调用(现在名称为“1234”)将导致查找包含hashCode 4对象的存储桶。由于只有一个存储桶包含hashCodes为6且没有hashCode为4的存储桶,因此无法存储桶找到并且不会尝试使用equals()方法。
当我用家里的地址搜索“你”时,如果我找不到你的房子,那么就没有比较家庭成员姓名的问题了。
这里的get()方法返回null。这是无法检索的损坏条目的情况,除非再次更改密钥内容以将其hashCode值恢复为6。
但如果get(k3)
k3
更改为长度为6的“654321”,则会找到存储桶并调用equals()以与放入的3个对象进行比较对于一个相同k3的对象,存储桶以及equals()都返回true。记住这里用来放置的同一个对象k3被改变了,所以改变也反映在HashMap中,它是同一个对象。这里==等式检查成功为k3并且没有调用equals()方法(这一点是从David Wallace的答案中获得的,在这一点上任何投票都请给他不是我!)。这里get()方法的结果将是期望的非null对象。这是因为密钥内容的更改保留了hashCode值为6。
现在,如果我们单独name
,又将Dog k4 = new Dog("123456");
更改为k3.name
。猜猜调用"654321"
时会发生什么!由于给定的键是一个单独的对象,因此将定位存储桶并且==失败,然后在地图存储桶中的所有3个对象上调用equals但是等于失败并且get(k4)的最终结果为get(k4)
。因为对象k3.name更改会影响存储在地图存储桶中的密钥,以便从“123456”更改为“654321”。当被要求找到“123456”时,没有键匹配“123456”,虽然最初使用该值,但它在地图中现在是“654321”。
因此,在用于存储在地图中之后更改关键对象内容是不安全的。这同样适用于HashSet也使用相同散列的Set实现。事实上,HashSet在场景后面使用了HashMap。
为了更好的表现。如果没有使用bucketing,则每次输入新条目时都必须与所有现有(先前放置)对象进行比较以确保唯一性。此外,在检索现有条目(调用get())时,它必须与HashMap中的所有对象进行比较,直到找到匹配项。使用上面的bucketing,它可以安全地忽略一些条目/对象,以进行相等性比较。
为了支持Hash实现中使用的这种机制,在为可以用于基于Hash存储的对象实现这些方法时,应该遵循false
和hashCode()
方法之间的规范/契约。实现。 JavaDoc here,请阅读。
PS:我在David Wallace回答之前从等号()部分拿起了==检查并编辑到这里。谢谢大卫的评论。
答案 5 :(得分:0)
此处的问题是,hashCode
基于name
String
的长度,而且您使用的值均为6。
HashMap
还有一个优化,它在使用equals()
之前执行引用相等。在这里,它不需要使用equals
,因为它是相同的关键对象。
答案 6 :(得分:0)
如果您将对象用作哈希键,则应将其设为immutable。正如您所见,更改用作哈希键的对象会导致意外行为,并且不可变意味着您无法在创建对象后对其进行更改。
答案 7 :(得分:0)
您必须使用使用不可变类(例如String
或包装类)作为Map
的键,否则Map的工作流程(由于其散列算法)将存在不一致。
您可以将自定义类(例如Dog
)设为不可变,以便稍后您无法更改已使用的密钥。
就像你用一把钥匙锁上一扇门,后来你用一些“工具”改变了同一把钥匙,然后期望打开锁着的门。