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

时间:2009-01-26 08:22:30

标签: java 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;
}

10 个答案:

答案 0 :(得分:22)

我认为不存在非平凡的哈希码。此外,您的equals()违反了一般合同stated in the API --- 不可传递

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

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

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


为了完整起见,我向您呈现Skeet - Niko证明=)

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

证明:让(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();
}