当equals()基于多个独立字段时,hashCode()方法

我有一个类的相等性基于2个字段,如果任何一个相等,那么这种类型的对象被认为是相等的。 我如何为这样的equals()编写一个hashCode()函数,以便当equals返回true时hashCode的一般契约保持不变?

public class MyClass { int id; String name; public boolean equals(Object o) { if (!(o instanceof MyClass)) return false; MyClass other = (MyClass) o; if (other.id == this.id || other.name == this.name) return true; return false; } } 

我该如何为这个类编写hashCode()函数? 我想避免像这样返回一个常数的简单案例:

 public int hashCode() { return 1; } 

我认为不存在非平凡的哈希码。 此外,您的equals()违反了API中所述的一般合同 – 它不具有传递性

(1,2)等于(1,3)

(4,3)等于(1,3)

但是(4,3) 不等于 (1,2)


为了完整起见,我向你呈现了Skeet – Nikocertificate=)

声明哈希码必须是普通的常量函数。

certificate :令(a,b)(c,d)是具有不同哈希码的两个对象,即h(a,b) ≠ h(c,d) 。 考虑对象(a,d) 。 根据OP的定义, (a,d)等于(a,b) ,并且(a,d)等于(c,d) 。 根据哈希码契约 , h(a,d) = h(a,b) = h(c,d) ; 矛盾。

好的,在您的方案中,忽略API要求一秒钟,没有非常量哈希函数

想象一下,有一个具有不同值的哈希函数

(a,b),(a,c),b!= c,然后散列(a,b)!=散列(a,c),eventhough(a,b)=(a,c)。

类似地,(b,a)和(c,a)必须发出相同的hashCode。

让我们调用哈希函数h。 我们发现:

h(x,y)= h(x,w)= h(v,w)forall x,y,v,w。

因此,执行所需操作的唯一hashFunction是常量。

我很确定Zach是对的 – 没有非平凡的哈希码可以做到这一点。

伪certificate:

考虑任何两个不相等的值,X =(id1,name1)和Y =(id2,name2)。

现在考虑Z =(id2,name1)。 这等于X和Y,因此必须具有与X和Y相同的哈希码。因此,X和Y必须具有相同的哈希码 – 这意味着所有值必须具有相同的哈希码。

你有一个奇怪的情况 – 你正在打破平等的传递性质。 X.equals(Z)和Z.equals(Y) 应该表示X.equals(Y) – 但事实并非如此。 你对平等的定义不适合平等的正常合同。

我想你不能。 原因是,你的equals()方法不是传递的。

传递性意味着三个非空x,y,z,如果x.equals(y)y.equals(z) ,则x.equals(z) 。 在您的示例中,对象x={id: 1, name: "ha"}y={id: 1, name: "foo"}z={id: 2, name: "bar"}具有此属性(x.equals(y) and y.equals(z)) 。 但是, x.equals(z)false 。 每个equals()方法都应该具有此属性,请参阅Java API文档。

返回散列函数:每个函数都产生由f(x)==f(y)定义的等价。 这意味着如果你对函数值的比较感兴趣并希望它返回true,如果x==y (可能在其他情况下),你将收到一个传递关系,这意味着你必须至少考虑一个传递闭包对象的等价性。 在你的情况下,传递闭包是微不足道的关系(一切都等于任何东西)。 这意味着您无法通过任何function区分不同的对象。

你是否故意将相等定义为当id相等或者名称相等时。“OR”不应该是“AND”吗?

如果你的意思是“AND”,那么你的哈希码应该使用相同或更少(但绝不使用equals未使用的字段)字段来计算你使用equals()。

如果你的意思是“或”那么你的hashgcode不应该在其哈希码计算中包含id或name,这实际上没有意义。

编辑:我没有仔细阅读这个问题。

我会用commons-lang jar。

XOR成员hashCode应该工作。 因为他们应该正确实现hashCode()和equals()。

但是,如果您不保护您的hashCode,您的代码可能会出错。 一旦它被散列,它就不应该被改变。 应该防止它发生。

 public hashCode(){ return new AssertionError(); } 

要么

  public class MyClass { final int id; final String name; // constructor } 

要么

 public class MyClass { private int id; private String name; boolean hashed=false; public void setId(int value){ if(hashed)throw new IllegalStateException(); this.id=value; } public void setName(String value){ if(hashed)throw new IllegalStateException(); this.name=value; } // your equals() here public hashCode(){ hashed=true; return new HashCodeBuilder().append(id).append(name).toHashCode(); } } 

重新阅读问题后。

当其中一个字段被更新时,您可以自动完成另一个字段。

编辑:我的代码可能比我的英语更好。

 void setName(String value){ this.id=Lookup.IDbyName(value); } void setID(String value){ this.name=Lookup.NamebyId(value); } 

编辑2:

问题上的代码可能会出错,因为除非您同时设置了id和名称,否则它将始终返回true。

如果您真的想要一个部分等于的方法,请创建自己的名为“partialEquals()”的API。

如果我们使用TreeMap替换HashMap怎么办? 是否可以为MyClass编写有效的compareTo()方法?

超级时刻。 在validationequals()契约之前尝试编写hashCode()。

最简单的路由是对每个字段的哈希码进行异或。 这在某些情况下会有轻微的丑陋(例如,在X,Y坐标中,当你翻转X和Y时,它会导致具有相同哈希的潜在不良情况),但整体上非常有效。 根据需要进行调整,以便在必要时提高效率。

这个怎么样

 public override int GetHashCode() { return (id.ToString() + name.ToString()).GetHashCode(); } 

该函数应该总是返回一个“有效”哈希…

编辑:只是注意到你使用“或”不是“和”:P我怀疑这个问题有什么好的解决方案……

怎么样

 public override int GetHashCode() { return id.GetHashCode() ^ name.GetHashCode(); }