我有一个类,它的相等性基于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;
}
答案 0 :(得分:22)
我认为不存在非平凡的哈希码。此外,您的equals()
违反了一般合同stated in the API --- 不可传递:
(1,2)
等于(1,3)
(4,3)
等于(1,3)
但(4,3)
不等于至(1,2)
。
声明:哈希码必须是普通的常量函数。
证明:让(a,b)
和(c,d)
成为具有不同哈希码的两个对象,即h(a,b) ≠ h(c,d)
。考虑对象(a,d)
。根据OP的定义,(a,d)
等于(a,b)
,(a,d)
等于(c,d)
。它来自hashcode contract h(a,d) = h(a,b) = h(c,d)
;矛盾。
答案 1 :(得分:8)
好的,在您的方案中,忽略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是不变的。
答案 2 :(得分:6)
我很确定Zach是对的 - 这样做没有非平凡的哈希码。
伪证明:
考虑任意两个不相等的值,X =(id1,name1)和Y =(id2,name2)。
现在考虑Z =(id2,name1)。这等于X和Y,因此必须具有与X和Y相同的哈希码。因此,X和Y必须具有相同的哈希码 - 这意味着所有值必须具有相同的哈希码。
有一个原因让你陷入一种奇怪的境地 - 你正在打破平等的过渡性质。 X.equals(Z)和Z.equals(Y)应该表示X.equals(Y) - 但事实并非如此。你对平等的定义不适合平等的正常契约。
答案 3 :(得分:2)
我想你不能。原因是,您的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)
定义的等价。这意味着如果您对函数值的比较感兴趣并希望它在x==y
(以及可能在其他情况下)中返回true,那么您将收到传递关系,这意味着您必须至少考虑传递函数关闭对象的等价性。在你的情况下,传递闭包是微不足道的关系(一切都等于任何东西)。这意味着您无法通过任何功能区分不同的对象。
答案 4 :(得分:2)
你是否故意将等同定义为当id相等或者名称相等时。“OR”不应该是“AND”吗?
如果您的意思是“AND”,那么您的哈希码应使用相同或更少(但绝不使用equals未使用的字段)字段来计算您使用equals()。
如果您的意思是“OR”,那么您的hashgcode不应该在其哈希码计算中包含id或name,这实际上没有意义。
答案 5 :(得分:0)
-
我会使用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();
}
}
答案 6 :(得分:0)
重新阅读问题后。
当其中一个字段被更新时,您可以自动完成另一个字段。
-
编辑:我的代码可能比我的英语更好。
void setName(String value){
this.id=Lookup.IDbyName(value);
}
void setID(String value){
this.name=Lookup.NamebyId(value);
}
编辑2:
问题上的代码可能会出错,因为除非您同时设置id和&名。
如果你真的想要一个部分等于的方法,那就创建一个名为“partialEquals()”的自己的API。
答案 7 :(得分:-1)
最简单的方法是对每个字段的哈希码进行异或。在某些情况下(例如在X,Y坐标中,当你翻转X和Y时,它会导致具有相同哈希值的可能很差的情况),但这总体上非常有效。根据需要进行调整,以便在必要时提高效率。
答案 8 :(得分:-1)
这个怎么样
public override int GetHashCode()
{
return (id.ToString() + name.ToString()).GetHashCode();
}
该函数应始终返回“有效”哈希...
编辑:只是注意到你使用“或”不是“和”:P我怀疑这个问题有什么好的解决方案......
答案 9 :(得分:-2)
怎么样
public override int GetHashCode()
{
return id.GetHashCode() ^ name.GetHashCode();
}