在我看到有关重写的equals和hashcode方法的所有问题中,人们总是说如果重写equals方法,你应该覆盖hashcode方法,反之亦然,以避免在从Hash集合中获取对象时出现问题。
就我个人而言,我没有看到反之亦然,我们认为我们使用一个对象(带有属性)作为HashMap中的键,我们不需要测试两个实例之间的相等性对象
如果我们以有效的方式覆盖哈希码方法(基于属性和其他规则),那么我们可以拥有唯一键,在这种情况下,对于HashMap,我们将在每个桶中具有唯一值,并且HashMap将不要使用equals方法来比较存储桶中的值,因为每个存储桶中都有一个值。
合同方:
我错了吗?
编辑(搜索之后,以下答案,并查看HashMap的源代码)
我们为什么还要覆盖equals方法?
初始条件,唯一的哈希码和没有等于()的重写
Object.equals()执行引用比较,此方法用于put和get方法
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;
}
如果我们不覆盖equals,如果我们丢失了密钥的引用,我们将无法从HashMap中检索值,例如对其他代码不可见的键引用。 读取HashMap的唯一方法是使用Iterator,并比较值以检索确切的对:
First put: [key:Instance1Obj(144756696), value:Person1Obj(“Baron”, “Steven”)]
Second put: [key:Instance2Obj(17488696), value:Person2Obj(“Hewlett”, “Emily”)]
如果我们想根据他的帐号检索Baron Steven的帐户,我们不仅可以创建一个具有相同帐号的新对象,例如Instance88Obj(144756696),并调用get方法来检索该值。在这种情况下,我们将得到一个null,因为get方法使用基于键引用的比较,即使哈希码是相同的。
如果未覆盖equals,则通过新的实例键(但具有相同的哈希码)放置一个新值以便更新它,将无法工作,只会添加另一对键/值:
First put: [key:Instance1Obj(144756696), value:Person1Obj(“Baron”, “Seven”)]
Second put: [key:Instance2Obj(144756696), value:Person2Obj(“Baron”, “Steven”)]
通常情况下,我们希望用“史蒂文”代替“七”,但替换不会发生
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
结论:
==&GT;如果覆盖哈希码,则覆盖等于。
==&GT;如果重写equals,也要覆盖哈希码。(在本主题之外:))
答案 0 :(得分:4)
如果您的班级使用equals
中的Object
方法,那么任何幂等hashCode
实施都可以。如果它使用equals
以外的某个超类的Object
方法,则需要确保您的hashCode
实现与之兼容。
答案 1 :(得分:2)
哈希码的值空间比哈希映射中的桶数大几个数量级。因此,除非我们讨论像Map
这样的专门EnumMap
实现,否则每个桶最终会有多个对象,即使给出了完美的哈希码。
不覆盖equals()
的对象只有当它们是完全相同的对象时才会被视为相等,而不管它们的字段值如何。因此,仅覆盖hashCode()
是没有意义的,因为它所能实现的只是轻微的性能损失(至少因为您的自定义哈希代码需要更长的时间来计算)。如果自定义哈希码不完美,它甚至可能是一个很大的性能损失。
每当您覆盖hashCode()
和equals()
时,您都会这样做,因为您希望对象的字段内容很重要。除非你重写两者,否则这种情况不会发生。
答案 2 :(得分:2)
在标准Java HashMap
中,equals
始终用于确定密钥是否在存储桶中。如果hashCode
只为一个项目提供了右侧存储区,但equals
对该一个键返回false,则表示密钥不存在。
如果不覆盖equals
,则使用对象引用。如果要将对象的值用作键(也就是说,可以使用具有相同值的不同对象实例),而不是要求完全相同的对象实例(相同的实例不相等),那么您需要编写equals
实现比较价值。
默认hashCode
与默认equals
一样高效。仅覆盖hashCode
可能会降低性能,而不会改变行为(equals
仍然确定两个关键对象是否相等)。这样做毫无意义。
你没有问过这个,但另外,如果你覆盖equals
而没有覆盖hashCode
,那么你打破 HashMap
键,因为{{1}可能会把你带到错误的桶,而hashCode
将找不到密钥,因为它只会检查一个桶。
你能做什么:
如果您可以为每个项目保证不同的equals
,则可以编写仅比较哈希码的hashCode
。如果将哈希代码缓存在对象中,则可以提高性能,使equals
只比较两个整数。当然还有内存开销。
答案 3 :(得分:1)
可以避免实现equals,或者避免实现hashcode。在这两种情况下,你都可以打开一个充满痛苦的世界。毕竟记得Hash用于将对象分成桶,然后桶中的对象仍然需要检查是否相等,你依赖于你正在使用的集合对象,就像你需要的那样。
这些准则可以防止出现很多问题,没有充分的理由可以避免跟随它们,除了尝试并且聪明一点,这样做可以在未来为自己打开一大堆问题。
答案 4 :(得分:1)
合同说如果两个对象相等,它们应该具有相同的哈希码。但另外,如果不相等的对象应该具有不同的哈希码,则哈希表最有效。
如果您覆盖默认的hashCode
方法而未覆盖默认的equals
方法,则可能会导致hashcode
的行为不理想;即给出更多情况,其中hashCode为不相等的对象返回相同的值。根据您的hashCode
覆盖和应用程序使用的实例的详细信息,这可能会在您的哈希表中给您带来更多冲突。最终结果将是性能的可测量下降。
所以虽然&#34;反过来&#34;对于正确的应用行为,并非严格要求,建议仍然有效。最好是equal
和hashCode
方法具有匹配的语义。
答案 5 :(得分:0)
不同的哈希码导致不相等的对象==&gt;它说“领导”不是说“永远”。两个相等的对象总是返回相同的哈希码,但是如果不覆盖哈希码方法,则两个非相等的对象很难返回相同的哈希码。