如何保证equals()和hashCode()同步?

时间:2016-07-03 21:27:45

标签: java equals hashcode

我们正在编写一个类,它需要非常复杂的逻辑来计算equals()和hashCode()。与...一致的东西:

Tuple<string, string>

我们不构造这些对象,它们是从外部复杂系统的XML反序列化的。有20多种类型,根据类型数据可以忽略,或用子进行处理,或者没有子进行处理,每种类型节点的数据比较取决于类型。

我们创建了equals()和hashCode()以反映所有这些规则,但最近遇到了一个问题,即hashCode与equals不同步,导致将相等的对象添加到HashSet两次。 我相信HashMap(以及HashSet)在Java中以这种方式实现:https://en.wikipedia.org/wiki/Hash_table实现首先将对象放在基于hashCode的桶中,然后对每个桶进行检查等于。在不幸的情况下,2个相等的对象将进入不同的桶,它们将永远不会被equals()进行比较。通过“不同步”这里我的意思是他们进入不同的桶。

确保equals和hashCode不会失去同步的最佳方法是什么?

修改: 这个问题与What issues should be considered when overriding equals and hashCode in Java?不同 他们在那里询问一般指导,并且接受的答案不适用于我的情况。他们说“make equals和hashCode一致”,在这里我要问我到底是怎么做的。

4 个答案:

答案 0 :(得分:6)

Guava testlib库有一个名为EqualsTester的类,可用于为equals()hashCode()实现编写测试。

添加测试既可以帮助您确保代码现在正确,也可以确保在将来修改代码时保持正确。

答案 1 :(得分:5)

如果遍历算法足够复杂,您希望避免重复自己,请将算法隔离为equalshashCode都可以使用的方法。

我看到两种选择,(在通常情况下)在广泛适用和有效之间进行权衡。

概括地说,适用

第一个选项是编写一个通用的遍历方法,它接受一个功能接口,并在遍历的每个阶段回调它,所以你可以传入一个lambda或实例,其中包含你想要执行的实际逻辑遍历; Visitor pattern。那个界面想要有办法说&#34;停止遍历&#34; (例如,当equals知道答案是&#34;不等于&#34;)时,private boolean traverse(Visitor visitor) { while (/*still traversing*/) { if (!visitor.visitNode(thisNode)) { return false; } /*determine next node to visit and whether done*/ } return true; } 可以保释。 从概念上来说,看起来像是:

equals

然后hashCodethis使用它来实现等式检查或哈希码构建,而不必知道遍历算法。

我在上面选择让方法返回一个标志,表示遍历是否提前结束,但这是一个设计细节。您可能不会返回任何内容,或者可能会返回equals进行链接,无论您的情况如何。

然而,问题在于使用它意味着分配一个实例(或者使用lambda但是你可能需要为lamba分配一些内容来进行更新,以便跟踪它正在做什么)并做一个很多方法调用。在你的情况下,这可能很好;也许它是性能杀手,因为你的应用需要经常使用equals。 : - )

具体而有效

...所以你可能想写一些特定于这种情况的东西,编写一些内置hashCodehashCode逻辑的东西。它将在equals使用时返回哈希码,或者private int equalsHashCodeWorker(Object other, boolean forEquals) { int code = 0; if (forEquals && other == null) { // not equal } else { while (/*still traversing*/) { /*update `code` depending on the results for this node*/ } } return code; } 的标志值(0 =不等于,!0 =相等)。不再普遍有用,但它避免了创建一个访问者实例传入/ lambda开销/调用开销。 概念,这可能类似于:

other

具体而言,具体针对您的案例以及您的风格指南等。有些人会让equals参数有两个目的(标志和&#34;其他&#34;对象),让other == null处理null个案本身并且只调用这个工作者当它有一个非hashCode对象时。我宁愿避免加倍这样的论点的含义,但你经常看到它。

测试

无论你走哪条路,如果你在一家拥有测试文化的商店里,你自然会想要为你已经看过的复杂案例以及其他案例进行测试。你看到失败的机会。

关于hashCode

的附注

无论如何,如果您希望调用hashCode很多,您可以考虑将结果缓存到实例字段。如果您正在使用的对象是可变的(并且听起来像是这样),那么只要您改变对象的状态,就会使存储的哈希码无效。这样,如果对象没有更改,则不必在后续调用c=0 x=[] y=[] z=[] """o=int(input("enter menu:"))""" while True: print("o=1:::add ","o=2:::max ","o=3:::find ","o=4:::remove ","o=5:::report ","o=6:::exit") o=int(input("enter menu:")) if o==1: x.append(input("enter name:")) z.append(int(input("enter #:"))) y.append(int(input("enter score:"))) c+=1 elif o==2: i=1 for i in range(len(x)): y[i]= y[i] if y[i]>y[i-1] else y[i-1] """i+=1""" f=y[i-1] print("max=",y[i-1]) elif o==3: n=int(input("enter number:")) for i in range(len(x)): if n==z[i]: print(x[i],y[i]) else: print("not found") elif o==4: p=int(input("enter student number:")) for i in range(len(x)): if p==z[i]: x.pop(i) y.pop(i) z.pop(i) elif o==5: for i in range(len(x)): print("name:",x[i],"st number:",z[i],"score:",y[i]) elif o==6: break 时重复遍历。但是,当然,如果你忘记在mutator方法的一个中使哈希码无效......

答案 2 :(得分:5)

condsider的一个选项可能是代码生成。基本上,您写出了需要比较的事物列表,并且有一个生成equals方法和hashcode方法的程序。由于两种方法都是从相同的事物列表中生成的,因此它们不应该不同步(前提是各个元素不一致)。

答案 3 :(得分:0)

如果a.equals(b),则表示a.hashcode() == b.hashcode()

但是,要小心!a.equals(b) 暗示a.hashcode() != b.hashcode()

这主要是因为哈希冲突可能是一个严重的问题,具体取决于您的算法和大量因素。通常,如果两个对象相等,则其哈希码始终相等。但是,您无法仅通过比较哈希码来确定两个对象是否相等,因为a.hashode() == b.hashcode()暗示a.equals(b)