针对特定情况在Java中覆盖hashCode

时间:2011-05-04 17:32:20

标签: java hashcode overwrite

我知道在使用hashCode和equals时还有其他关于一般最佳实践的问题,但我有一个非常具体的问题。

我有一个类,它有一个实例变量,一个同一个类的数组。更明确一点,这是代码:

Class Node{
    Node arr[] = new Node[5];
}

我需要覆盖类Node的hashCode,并且数组是确定两个节点是否相同的重要决定因素。如何有效地将数组合并到hashCode的计算中?

- 编辑 -

我正在尝试检查两个节点是否相同,这意味着它们具有相同数量的子节点,并且这些子节点导致完全相同的状态。因此,我正在有效地尝试比较两个节点的子树。我想知道我是否可以使用散列来进行此等式检查。

我认为我实际上需要对整个子树进行哈希处理,但考虑到我的类定义的递归性质,我不确定如何去做。

5 个答案:

答案 0 :(得分:4)

包含http://download.oracle.com/javase/6/docs/api/java/util/Arrays.html#hashCode(java.lang.Object [])作为hashCode()实现的一部分。< / p>

答案 1 :(得分:2)

  

我正在尝试检查两个节点   是相同的,意味着他们有   相同数量的孩子,那   那些孩子导致完全相同   状态。因此,我是有效的   试图比较在的子树   两个节点。我想知道我是否可以使用   哈希进行这种平等检查。

不,不应使用散列来检查相等性。这不是它的目的。它最终可以帮助你找出对象是否不相等,但如果它们相等则不会告诉你任何事情。

相同的对象将生成相同的哈希值,但是两个不相等的对象也可以生成相同的哈希值。换句话说,如果哈希值不同,您可以确定对象是不同的。就是这样。

如果要测试相等性,则需要实现equals。在您的情况下,存在一种危险,即您的方法将递归并引发堆栈溢出。如果您的对象包含对自身的引用怎么办?

如果你想生成一个哈希,你可以考虑数组的大小(以及它是否为空),但我不会尝试使用数组中对象的哈希值,因为潜在的无限循环。它并不完美,但它足够好。

还有另一种可能提供良好结果的激进方法。不是动态计算哈希值,而是为每个Node对象实例设置一个随机int值(我的意思是在创建时一次性并且总是返回该值)。在您的情况下,通过获取数组中对象实例的哈希值,您不会冒无限循环的风险。

如果散列等于,则需要开始比较数组对象实例。

REM:如果节点包含其他属性,则计算这些其他属性的哈希并忘记数组。当且仅当两个对象之间的哈希相同时才开始调查数组内容/大小。

REM2:评论提及DAG图表,这意味着我们不会遇到递归问题。但是,这种情况还不足以保证deepHashCode()能够成功。而且,这也太过分了。有一种更有效的方法可以解决这个问题。

如果Node 使用的哈希方法使用该数组计算哈希值,则deepHashCode()可能有效。但它效率不高。如果哈希方法使用其他节点属性,那么这些属性也必须相等。

有一种更快的方法来比较节点的相等性。使用唯一编号标记每个节点实例。然后,为了比较两个节点,首先比较它们的数组大小。如果它是等于,则使用它们的唯一编号比较每个阵列的节点。如果一个数组没有“拥有”另一个节点,那么我们就不会处理相同的节点。这个解决方案比递归更快。

答案 2 :(得分:1)

这取决于你的平等标准是什么。数组中的顺序是否重要?如果是这样,您可能希望使哈希代码取决于数组中节点的顺序。如果没有,您可能希望执行类似于对阵列中所有节点的哈希码进行异或操作的操作。大概有些值可能为空(所以要小心)。

基本上,您需要一致地覆盖hashCodeequals,这样如果两个对象相等,它们将具有相同的哈希码。这是黄金法则。

Eric Lippert有一个great blog post about GetHashCode in .NET - 这个建议同样适用于Java。

需要注意的一个潜在问题 - 如果你的节点中有一个循环(节点B的数组中出现的节点A的引用,反之亦然)你可能最终在哈希码计算中有一个循环太

答案 3 :(得分:1)

您可以使用Arrays.hashCode()Arrays.equals()方法。

答案 4 :(得分:0)

如果表现有任何问题,可以添加到当前答案中的一些要点。

首先,您需要确定节点中子节点的 order 是否重要。如果他们不这样做,则不能将哈希码用于数组。考虑围绕java.util.Set定义的哈希码函数。还要考虑在内部使用一些排序来提高性能。例如,如果子树的深度/高度不同,则可以按深度排序。

其次,如果您的子树很深,那么您的哈希码会变得非常昂贵。所以我会缓存哈希码,并在构造时计算它(如果你的节点是不可变的),或者在变异时失效并按需重新计算。

第三,如果您的子树很深,请检查equals()中的哈希码并提前返回false。是的,哈希码由Map实现进行检查,但是有些地方代码只是使用equals()来比较两个对象,并且它们可能需要付出很大的代价。

最后,考虑使用Arrays.asList()(如果子命令很重要)或HashSet(如果排序无关紧要且没有两个子节点相等)而不是简单数组。然后减少equals和hashcode以将调用委托给容器实例......当然还有适当的哈希码缓存。