Question

i have a class whose equality is based on 2 fields such that if either one is equal then the objects of this type are considered equal. how can i write a hashCode() function for such an equals() so that the general contract of hashCode being equal when equals returns true is preserved?

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;
  }
}

how do i write a hashCode() function for this class? and i want to avoid the trivial case here of returning a constant like so:

public int hashCode() {
  return 1;
}
Was it helpful?

Solution

I don't think a nontrivial hashcode exists. Also, your equals() violates the general contract as stated in the API --- it is not transitive:

(1,2) equals (1,3)

(4,3) equals (1,3)

But (4,3) is not equal to (1,2).


For the sake of completeness, I present to you the Skeet-Niko proof =)

Claim: The hashcode must be the trivial constant function.

Proof: Let (a,b) and (c,d) be two objects with distinct hashcodes, i.e. h(a,b) ≠ h(c,d). Consider the object (a,d). By the OP's definition, (a,d) is equal to (a,b), and (a,d) is equal to (c,d). It follows from the hashcode contract that h(a,d) = h(a,b) = h(c,d); a contradiction.

OTHER TIPS

Ok, in your scenario, ignoring the API requirements for a second, there is no non-constant hash function

Imagine there were a hashfunction which has different values for

(a,b), (a,c), b!=c, then hash(a,b) != hash(a,c), eventhough (a,b) = (a,c).

Similarly, (b,a) and (c,a) must emit the same hashCode.

Let's call our hash-function h. We find:

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

Hence, the only hashFunction that does what you want is constant.

I'm pretty sure Zach's right - there's no non-trivial hashcode to do this.

Pseudo-proof:

Consider any two non-equal values, X=(id1, name1) and Y=(id2, name2).

Now consider Z=(id2,name1). This is equal to both X and Y, so must have the same hashcode as both X and Y. Therefore X and Y must have the same hashcode - which means all values must have the same hashcode.

There's a reason why you've got into a weird situation - you're breaking the transitive nature of equals. The fact that X.equals(Z) and Z.equals(Y) should mean that X.equals(Y) - but it doesn't. Your definition of equality isn't suitable for the normal contract of equals.

I think you can't. The reason is, your equals() method is not transitive.

Transitivity means for three non-null x, y, z, if x.equals(y), y.equals(z), then x.equals(z). In your example, an object x={id: 1, name: "ha"}, y={id: 1, name: "foo"}, z={id: 2, name: "bar"} have this property (x.equals(y) and y.equals(z)). However, x.equals(z) is false. Every equals() method should have this property, see the Java API docs.

Back to hashing functions: Every function yields an equivalence defined by f(x)==f(y). That means if you are interested in comparison of the function values and want it to return true if x==y (and possibly in other cases), you'll receive a transitive relation, which means you have to consider at least a transitive closure of objects' equivalence. In your case, the transitive closure is the trivial relation (everything equals to anything). Which means you can't distinguish different objects by any function.

Have you intentionally defined equality as when ids are equal OR names are equal.. Shouldnt the "OR" be a "AND" ?

If you meant "AND" then your hashcode should be calculated using the very same or less (but never use fields not used by equals) fields you are by equals().

If you meant "OR" then you r hashgcode should not include id or name in its hashcode calculation which doesnt really make sense.

EDIT: I didn't read the question carefully.

--

I'll use commons-lang jar.

XOR the members hashCode should works. As they should implements hashCode() and equals() correctly.

However, your code may wrong if you don't protect your hashCode. Once it was hashed, it should not be changed. It should be prevented from being happen.

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

or

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

or

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();
   }
}

After re-read the question.

You may auto-complete the another field when one of them being updated.

--

EDIT: My code may say better then my English.

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

EDIT 2:

The code on the question may wrong as will always return true unless you've set both the id & name.

If you really want a method that do partial equals, create you own API that named "partialEquals()".

The simplest route is to XOR the hashcodes of each individual field. This has minor ugliness in some situations (e.g. in X,Y coordinates, it causes the potentially poor situation of having equal hashes when you flip X and Y), but is overall pretty effective. Tweak as needed to reduce collisions if necessary for efficiency.

How about this

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

The function should allways return a "valid" hash...

Edit: just noticed that you use "or" not "and" :P well i doubt there is any good solution to this problem...

How about

public override int GetHashCode()
{
    return id.GetHashCode() ^ name.GetHashCode();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top