11

我有一个类,其相等性基于 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;
}
4

10 回答 10

24

我认为不存在重要的哈希码。此外,您equals()违反了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)。从哈希码合约可以看出h(a,d) = h(a,b) = h(c,d);一个矛盾。

于 2009-01-26T08:33:12.487 回答
9

好的,在您的场景中,暂时忽略 API 要求,没有非常量哈希函数

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

(a,b), (a,c), b!=c, 然后 hash(a,b) != hash(a,c),尽管 (a,b) = (a,c)。

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

让我们称我们的散列函数为 h。我们发现:

h(x,y) = h(x,w) = h(v,w) 对于所有 x,y,v,w。

因此,唯一能做你想做的事情的 hashFunction 是常量。

于 2009-01-26T08:56:09.570 回答
6

我很确定 Zach 是对的——没有重要的哈希码可以做到这一点。

伪证明:

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

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

你陷入一个奇怪的境地是有原因的——你打破了equals的传递性。X.equals(Z) 和 Z.equals(Y)应该意味着 X.equals(Y) - 但事实并非如此。您对平等的定义不适合正常的平等合同。

于 2009-01-26T08:39:25.360 回答
2

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

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

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

于 2009-01-26T08:39:53.253 回答
2

您是否有意将平等定义为 id 相等或名称相等时。“OR”不应该是“AND”吗?

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

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

于 2009-01-26T10:26:22.227 回答
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();
   }
}
于 2009-01-26T09:10:59.837 回答
0

重新阅读问题后。

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

--

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

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

编辑2:

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

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

于 2009-01-26T09:18:06.687 回答
-1

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

于 2009-01-26T08:24:45.610 回答
-1

这个怎么样

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

该函数应始终返回“有效”哈希...

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

于 2009-01-26T08:37:33.367 回答
-2

怎么样

public override int GetHashCode()
{
    return id.GetHashCode() ^ name.GetHashCode();
}
于 2009-01-26T08:25:23.893 回答