在Java中编写哈希函数的最佳实践是什么?

45

我想知道在Java中编写#hashCode()方法的最佳实践是什么。 可以在这里找到一个好的描述。它是否很好?


可能是 https://dev59.com/oHVD5IYBdhLWcg3wBm1g 的重复问题。 - Yishai
我认为这篇帖子的范围比较广,不完全符合当前的情况。 - Denys S.
请问您能否澄清一下您的问题与之前的问题有何不同呢?在我看来它们看起来是一样的。 - Michael Myers
@mmyers:如果我们谈论问题,那么它们的区别至少在于它们的表述方式。至于内容,那里的许多答案确实回答了这个问题。 - Denys S.
可能是https://dev59.com/1E3Sa4cB1Zd3GeqPvXvT的重复问题,定义对象的哈希码为求和乘法是否不正确? - polygenelubricants
3个回答

60

以下是来自《Effective Java第二版》第9项的引用:“当你重写equals方法时,总是要重写hashCode方法”:

虽然本条目中的配方可以产生相当不错的哈希函数,但它并不能产生最先进的哈希函数,而且截至1.6版本,Java平台库也没有提供这样的哈希函数。编写这样的哈希函数是一个研究课题,最好由数学家和计算机科学家来完成。[...尽管如此,]本条目中描述的技术对于大多数应用程序来说应该是足够的。

Josh Bloch的配方

  • 在一个名为resultint变量中存储一些常数非零值,比如17。
  • 为定义equals的每个字段f计算一个int哈希码c
    • 如果字段是boolean类型,则计算(f ? 1 : 0)
    • 如果字段是byte, char, short, int类型,则计算(int) f
    • 如果字段是long类型,则计算(int) (f ^ (f >>> 32))
    • 如果字段是float类型,则计算Float.floatToIntBits(f)
    • 如果字段是double类型,则先计算Double.doubleToLongBits(f),然后像上面那样哈希结果的long
    • 如果字段是对象引用并且该类的equals方法通过递归调用equals来比较该字段,则对该字段递归调用hashCode。如果该字段的值为null,则返回0
    • 如果字段是数组,则将其视为每个元素都是单独的字段。如果数组字段中的每个元素都是重要的,则可以使用发布1.5中添加的Arrays.hashCode方法之一。
  • 将哈希码c组合到result中,如下所示:result = 31 * result + c;

现在,当然那个配方相当复杂,但幸运的是,由于java.util.Arrays.hashCode(Object[]),您不必每次重新实现它。

@Override public int hashCode() {
    return Arrays.hashCode(new Object[] {
           myInt,    //auto-boxed
           myDouble, //auto-boxed
           myString,
    });
}

从Java 7开始,java.util.Objects.hash(Object...)提供了一个方便的可变参数变体。


24
一个实现hashCode()的很好的参考资料可以在书籍Effective Java中找到。当你理解了生成良好哈希函数的理论之后,可以查看Apache commons lang提供的HashCodeBuilder,该类实现了书中所述的内容。从文档中可以看出:

该类使得可以为任何类构建一个好的hashCode方法。它遵循Joshua Bloch在书籍Effective Java中制定的规则。编写一个好的hashCode方法实际上相当困难。此类旨在简化该过程。


0

正如@leonbloy所说,理解它很重要。即便如此,一个“最佳”实践是让你的IDE为你编写函数。在某些情况下,它可能不是最优的 - 在极少数情况下甚至可能不好 - 但对于大多数情况而言,它易于操作、可重复、无误差,并且足够好(就像哈希码一样)。当然,阅读文档并充分理解它 - 但不要不必要地使它变得复杂。


是的,在使用EJB或类似情况下,使用实体不是最优解决方案。 - Denys S.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接