我应该覆盖集合的hashCode()吗?

时间:2015-10-15 19:44:42

标签: java collections hashcode

鉴于我的课程中包含各种字段:

class MyClass {
    private String s;
    private MySecondClass c;
    private Collection<someInterface> coll;
    // ...

    @Override public int hashCode() {
        // ????
    }
}

其中,我确实有各种各样的对象,我想在HashMap中存储。为此,我需要hashCode() MyClass

  1. 我必须以递归的方式进入所有字段和各个父类 ,以确保它们都正确实现hashCode(),否则hashCode() MyClass可能不会考虑某些值。这是对的吗?

  2. 我该怎么处理Collection?我可以一直依赖hashCode()方法吗?是否会考虑我的someInterface对象中可能存在的所有子值?

  3. 我打开了关于在此处唯一身份识别对象的实际问题的第二个问题:How do I generate an (almost) unique hash ID for objects?

    澄清:

      

    你班上有什么或多或少的不合理吗?字符串s?然后只将其用作哈希码。

    如果其中一个对象的coll中的任何值发生了变化,那么两个对象的MyClass hashCode()肯定会有所不同。如果两个对象的所有字段都存储相同的值,HashCode应该只返回相同的值。基本上,在MyClass对象上进行一些耗时的计算。如果计算已经使用完全相同的值在前一段时间完成,我想多余时间。为此,如果结果已经可用,我想查看HashMap。

      

    你会在HashMap中使用MyClass作为键还是值?如果是键,则必须覆盖equals()和hashCode()

    因此,我在HashMap中使用hashCode OF MyClass作为。值(计算结果)将是不同的,如整数(简化)。

      

    您认为平等对多个馆藏意味着什么?它应该依赖于元素排序吗?它应该只取决于存在的绝对元素吗?

    这取决于coll中存储的Collection类型吗?虽然我觉得订购并不重要,但没有

    您从本网站获得的回复非常华丽。谢谢大家

      

    @AlexWien取决于该集合的项目是否属于该类别的等同定义。

    是的,他们是。

4 个答案:

答案 0 :(得分:7)

  
      
  1. 我必须递归进入所有字段和各个父类递归,以确保它们都正确实现hashCode(),否则hashCode() MyClass可能没有考虑到某些值。这是对的吗?
  2.   

那是对的。它并不像听起来那么繁重,因为经验法则是,如果您覆盖hashCode(),则只需覆盖equals()。您不必担心使用默认equals()的类;默认hashCode()就足够了。

此外,对于您的课程,您只需要在equals()方法中对您比较的字段进行哈希处理。例如,如果其中一个字段是唯一标识符,则只需在equals()中检查该字段并在hashCode()中对其进行哈希处理即可。

所有这一切都取决于您是否覆盖equals()。如果你没有覆盖它,也不要为hashCode()而烦恼。

  
      
  1. 我如何处理Collection?我可以一直依赖hashCode()方法吗?是否会考虑我的someInterface对象中可能存在的所有子值?
  2.   

是的,您可以依赖Java标准库中的任何集合类型来正确实现hashCode()。是的,任何ListSet都会考虑其内容(它会将项目和哈希代码混合在一起)。

答案 1 :(得分:2)

因此,您希望对对象的内容进行计算,这将为您提供一个唯一的密钥,您可以在HashMap中检查&#34;重&#34;对于给定的深层字段组合,您已经完成了两次 想要做两次的计算。

仅使用hashCode

我认为hashCode不适合在您描述的场景中使用。

hashCode始终equals()关联使用。它是合同的一部分,而且它是一个重要的部分,因为hashCode()会返回一个整数,尽管有人可能会尝试使hashCode()尽可能地分布,但它对于同一个类的每个可能的对象都不会是唯一的,除非是非常具体的情况(IntegerByteCharacter很容易。 )。

如果您想亲眼看看,请尝试生成最多4个字母(大写和小写)的字符串,并查看其中有多少字符具有相同的哈希码。

因此,

HashMap在哈希表中查找内容时会同时使用hashCode()equals()方法。将会有一些具有相同hashCode()的元素,您只能通过使用equals()对您的班级测试所有元素来判断它们是否相同。

一起使用hashCodeequals

在这种方法中,您将对象本身用作哈希映射中的键,并为其提供适当的equals方法。

要实施equals方法,您需要深入了解所有字段。他们所有的班级都必须equals()符合您的想法,以便进行大规模计算。当对象实现接口时,需要特别小心。如果计算基于对该接口的调用,并且实现该接口的不同对象在这些调用中返回相同的值,那么它们应该以反映该值的方式实现equals

他们的hashCode应与equals匹配 - 当值相等时,hashCode必须相等。

然后,您可以根据所有这些项目构建equalshashCode。您可以使用Objects.equals(Object, Object)Objects.hashCode( Object...)来节省很多样板代码。

但这是一个好方法吗?

虽然您可以将hashCode()的结果缓存在对象中并重新使用它而不进行计算,只要您不改变它,就不能为{{1}执行此操作}。这意味着equals的计算将会很长。

因此,根据每个对象调用equals方法的次数,这将会加剧。

例如,如果您要在equals()中拥有30个对象,但是300,000个对象将会出现并与它们进行比较,只是为了意识到它们与它们相等,那么你就是&#39;将进行300,000次重磅比较。

如果你只有很少的实例,其中一个对象将具有相同的hashMap或落入hashCode中的同一个桶中,需要进行比较,那么就去HashMap方式可能效果很好。

如果你决定这样做,你需要记住:

如果对象是equals()中的某个键,只要它在那里就突变。如果您需要改变它,您可能需要对其进行深层复制并将副本保留在哈希映射中。深度复制需要考虑内部的所有对象和接口,以查看它们是否可以复制。

为每个对象创建唯一键

回到最初的想法,我们已经确定HashMap不适合哈希映射中的密钥。一个更好的候选者是哈希函数,例如hashCodemd5(或更高级的哈希值,例如sha256,但你不需要在你的情况下加密强度),其中碰撞是比sha1更为罕见。您可以获取类中的所有值,将它们转换为字节数组,使用此类哈希函数对其进行哈希处理,并将其十六进制字符串值作为映射键。

当然,这不是一个微不足道的计算。因此,您需要考虑它是否真的为您节省了大量时间,而不是您想要避免的计算。它可能比重复调用int来比较对象更快,因为每个实例只执行一次,并且在&#34;大计算&#34;时具有的值。

对于给定的实例,除非改变对象,否则可以缓存结果而不再计算结果。或者你可以在进行&#34;大计算&#34;。

之前再次计算它

然而,您需要&#34;合作&#34;你班上所有的物品。也就是说,它们都需要合理地转换为字节数组,使得两个等效对象产生相同的字节(包括与上面提到的接口对象相同的问题)。

你还应该注意你拥有的情况,例如,两个字符串&#34; AB&#34;和&#34; CD&#34;这将给你与&#34; A&#34;相同的结果。和&#34; BCD&#34;,然后你最终得到两个不同对象的相同哈希值。

答案 2 :(得分:0)

供将来的读者使用。

是的,等于和hashCode齐头并进。

下面显示了使用帮助程序库的典型实现,但实际上显示了“手拉手”性质。 apache的帮助程序库使事情更简单,恕我直言:

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }
    MyCustomObject castInput = (MyCustomObject) o;

    boolean returnValue = new org.apache.commons.lang3.builder.EqualsBuilder()
            .append(this.getPropertyOne(), castInput.getPropertyOne())
            .append(this.getPropertyTwo(), castInput.getPropertyTwo())
            .append(this.getPropertyThree(), castInput.getPropertyThree())
            .append(this.getPropertyN(), castInput.getPropertyN())
            .isEquals();

    return returnValue;
}

@Override
public int hashCode() {
    return new org.apache.commons.lang3.builder.HashCodeBuilder(17, 37)
            .append(this.getPropertyOne())
            .append(this.getPropertyTwo())
            .append(this.getPropertyThree())
            .append(this.getPropertyN())
            .toHashCode();
}    

17,37 ..您可以选择自己的值。

答案 3 :(得分:-1)

从你的澄清:

您希望将MyClass存储在HashMap作为密钥。 这意味着添加对象后不允许更改hashCode()。 因此,如果您的集合可能在对象实例化后发生更改,则它们不应该是hashcode()的一部分。

来自http://docs.oracle.com/javase/8/docs/api/java/util/Map.html

  

注意:如果将可变对象用作地图,则必须非常小心   键。如果对象的值,则不指定映射的行为   以一种影响等于比较的方式改变   对象是地图中的一个关键。

对于20-100个对象,不值得您输入hash()或equals()实现不一致的风险。

在您的情况下,无需覆盖hahsCode()和equals()。 如果你不克服它,java会获取equals和hashcode()的唯一对象标识(这是有效的,因为你声明你不需要等于()考虑对象字段的值)。

使用默认实现时,您可以放心使用。

在插入后哈希码更改时,使用自定义哈希码()作为HashMap中的键时出错,因为您使用集合的哈希码()作为对象哈希码的一部分可能会导致极难找到错误

如果您需要确定重量计算是否已完成,我不会 absue equals()。只需编写一个自己的方法objectStateValue()并在集合上调用hashcode()。这样就不会干扰对象hashcode和equals()。

public int objectStateValue() {
    // TODO make sure the fields are not null;
 return 31 * s.hashCode() + coll.hashCode();
}

另一个更简单的可能性:执行耗时计算的代码可以在计算准备就绪后立即将calculateCounter提高一。然后,您只需检查计数器是否已更改。这更便宜,更简单。