重写等于非有效散列键的强制性

时间:2013-12-26 00:11:05

标签: java hashmap key equals hashcode

在我看到有关重写的equals和hashcode方法的所有问题中,人们总是说如果重写equals方法,你应该覆盖hashcode方法,反之亦然,以避免在从Hash集合中获取对象时出现问题。

就我个人而言,我没有看到反之亦然,我们认为我们使用一个对象(带有属性)作为HashMap中的键,我们不需要测试两个实例之间的相等性对象

如果我们以有效的方式覆盖哈希码方法(基于属性和其他规则),那么我们可以拥有唯一键,在这种情况下,对于HashMap,我们将在每个桶中具有唯一值,并且HashMap将不要使用equals方法来比较存储桶中的值,因为每个存储桶中都有一个值。

合同方:

  • 我们的对象的两个实例是equals(Object.equals one)意味着它们具有相同的引用,并且基于我们的hashcode方法,hashcode将是相同的==>行
  • 不同的哈希码导致不相等的对象==> NOK(在我们的例子中可以违反这个规则)但是由于我们有很好的哈希键(我们的场景目的),合同并不重要

我错了吗?

编辑(搜索之后,以下答案,并查看HashMap的源代码)

我们为什么还要覆盖equals方法?

初始条件,唯一的哈希码和没有等于()的重写

  • 使用get()方法问题检索值:

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方法使用基于键引用的比较,即使哈希码是相同的。

  • 使用put()方法问题更新值:

如果未覆盖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,也要覆盖哈希码。(在本主题之外:))

6 个答案:

答案 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;对于正确的应用行为,并非严格要求,建议仍然有效。最好是equalhashCode方法具有匹配的语义。

答案 5 :(得分:0)

不同的哈希码导致不相等的对象==&gt;它说“领导”不是说“永远”。两个相等的对象总是返回相同的哈希码,但是如果不覆盖哈希码方法,则两个非相等的对象很难返回相同的哈希码。