当前位置: 首页 > 知识库问答 >
问题:

重写等于对于高效哈希键不是必需的

佴英奕
2023-03-14

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

就我个人而言,我认为反之亦然。让我们考虑一下,我们使用一个对象(带有属性)作为HashMap中的键,并且我们不需要测试该对象的两个实例之间的相等性。

如果我们以一种有效的方式(基于属性和其他规则)重写hashcode方法,那么我们就可以拥有唯一的键,在这种情况下,对于HashMap来说,我们将在每个bucket中拥有唯一的值,并且HashMap将不会使用equals方法来比较bucket中的值,因为我们在每个bucket中都有一个值。

合同方:

  • 我们对象的两个实例是equals(theObject.EqualsOne)意味着它们具有相同的引用,并且根据我们的hashcode方法,hashcode将是相同的==>OK
  • 不同的hashcode导致不相等的对象==>NOK(在我们的情况下,有可能违反这一规则),但由于我们有好的hashing键(我们的场景目的),因此合同实际上并不重要

我错了吗?

编辑:(搜索后,答案下方,并查找HashMap的源代码)

为什么我们也要重写equals方法?

    null
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的唯一方法是使用迭代器,再加上比较值以检索精确的对:

First put: [key:Instance1Obj(144756696),  value:Person1Obj(“Baron”, “Steven”)]
Second put: [key:Instance2Obj(17488696),  value:Person2Obj(“Hewlett”, “Emily”)]

如果我们想根据Baron Steven的帐号检索他的帐号,我们不能仅仅创建一个具有相同帐号的新对象(例如Instance88Obj(144756696)并调用get方法来检索值。在这种情况下,我们将得到一个空结果,因为get方法使用了基于键引用的比较,即使hashcode将是相同的。

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

如果未重写equals,则通过一个新的实例键(但具有相同的hashcode)来更新该值将不起作用,只会添加另一对键/值:

First put: [key:Instance1Obj(144756696),  value:Person1Obj(“Baron”, “Seven”)]
Second put: [key:Instance2Obj(144756696),  value:Person2Obj(“Baron”, “Steven”)]

通常我们希望用“steven”代替值“seven”,但这种替换不会发生

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;
}

结论:

共有1个答案

王高超
2023-03-14

在标准的Javahashmap中,总是使用equals来确定密钥是否在bucket中。如果hashcode只给出了一个项的right bucket,但equals针对该项返回false,这意味着该项不在其中。

如果不重写equals,它将使用对象引用。如果您希望使用对象的值作为键(也就是说,您可以使用具有相同值的不同对象实例),而不需要完全相同的对象实例(相同的实例不相等),那么您需要编写equals来实现比较值。

默认hashcode与默认equals一样高效。仅重写hashcode可能会降低性能,而不改变行为(equals仍然确定两个关键对象是否相等)。这样做毫无意义。

您没有这样问,但是另外,如果您重写equals而不重写hashcode,那么您就会破坏hashmap键,因为hashcode可能会将您带到错误的bucket,equals将找不到键,因为它只检查一个bucket。

您可以做的:如果您可以保证每个项目都有不同的hashcode,那么您可以编写equals,它只比较哈希代码。如果在对象中缓存哈希代码,那么这可以提高性能,使equals只比较两个int。当然还有内存开销。

 类似资料:
  • 问题内容: 您能否澄清一下,为什么在我们将 final 关键字设为不变时,为什么 在上课之前需要 final 关键字。我的意思是,如果我们将所有属性声明为私有和最终的,那么它也是一个不可变的类,不是吗? 很抱歉,这个问题似乎很简单,但是我对此感到非常困惑。帮帮我。 编辑:我知道一个声明为final的类不能被子类化。但是如果每个属性都是私有和final的,那有什么区别呢? 问题答案: 正如堆纸器所说

  • 标题说明了一切。我的问题是关于不同的字符串等价方法的效率。我经常使用<代码>。equalsIgnoreCase(String str),因为我对它很有兴趣。但我开始怀疑这是否是寻找字符串之间等价性的最有效方法。在我看来,正在调用一种大小写转换方法或,然后在其定义中调用,但我可能错了。那么,以下哪种方法在以下情况或任何情况下更有效?

  • 问题内容: 我必须删除列表中的重复对象。这是来自对象Blog的列表,如下所示: 复制的对象是标题,作者,URL和描述与其他对象相同的对象。 而且我无法更改对象。我不能在上面放新方法。 我该怎么做呢? 问题答案: 如果您不能编辑类的源代码(为什么不这样做),则需要遍历列表并根据提到的四个条件(“标题,作者,URL和描述”)比较每个项目。 为了以一种高效的方式做到这一点,我将创建一个新类,类似于包含这

  • 问题内容: 我正在阅读Java 1.6 API提供的HashMap类的代码,无法完全理解以下操作的需要(位于put和get方法的主体中): 该方法具有以下主体: 通过对提供的哈希码执行位操作,可以有效地重新计算哈希。即使API声明如下,我也无法理解这样做的必要性: 这很关键,因为HashMap使用2的幂的哈希表,否则哈希表在低位无差异时会遇到冲突。 我确实知道键值参数存储在数据结构数组中,并且该数

  • 问题内容: 我正在从xml配置转移到注释。我想转换一个会话范围的bean是 可以通过注释完成此操作吗?如果没有,我该怎么做才能使该声明继续工作? 问题答案: 在spring上下文xml中,执行以下操作: 请注意,尽管如此,你将需要为该包中的所有类编写接口。

  • 我正在开发一款Android应用程序。在我的应用程序中,我集成了Facebook登录。我的facebook登录工作正常。但当我制作release apk并运行该应用程序并尝试登录Facebook时,它就不工作了。请看下面我的场景。 我生成如下的发布apk 然后我使用jks文件路径生成keyhash。 我得到了一个散列键,然后将其添加到开发人员配置文件设置中。 当我在我的设备上安装并运行apk并使用