views:

895

answers:

12

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

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.

Brian
This doesn't fit the question - which I'd misread as well. If either of the fields changes, the hashcode will change.
Jon Skeet
Tom
A: 

How about

public override int GetHashCode()
{
    return id.GetHashCode() ^ name.GetHashCode();
}
Ed Swangren
This doesn't answer the question correctly - the hash code needs to be the same when only one of the fields is the same. Your answer requires both fields to provide the same hash for equality.
ck
Geez, you're right, didn't give it enough thought.
Ed Swangren
I hadn't read the question properly either...
Jon Skeet
Downvote removed after edit
ck
+19  A: 

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.

Zach Scrivena
Darn your speedier edit than my frantic typing :)
Jon Skeet
You don't really need the contradiction. You could shorten it to: given two objects (a,b), (c,d). We find that h(a,b) = h(a,d) = h(c,d).
nes1983
Thanks, this made me think harder about my own GetHashCode() routines.
Ed Swangren
A: 

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...

Petoj
Nope - that will produce different hashes for values which vary in one field but not the other.
Jon Skeet
+5  A: 

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.

Jon Skeet
Thank you for the endorsement! =)
Zach Scrivena
umm, this is, like, exactly what zach said, only more bloated.
nes1983
"Bloated" in this case means more informative, in my humble opinion atleast. Zach has the link going for him though.
Matthew Scharley
Niko: I was writing this while Zach was editing his. When I started writing it, Zach's answer was just "I don't think a nontrivial hashcode exists." And yet you've written *another* answer with the same information in...
Jon Skeet
jon: alright, i didn't know he changed his post.
nes1983
and: my post wasn't telling him about this transitivity-stuff. i mean, i don't care all that much what the API says. I mean: I have work to do. there could be cases and dictionary implementations that don't rely on transitivity. i presume. anyway, there is no non-constant hash for him, i think.
nes1983
+1 for the proof, which I have shamelessly used as the basis for mine.
Zach Scrivena
+2  A: 

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.

jpalecek
+7  A: 

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.

nes1983
+1 for the proof, which I have shamelessly used as the basis for mine.
Zach Scrivena
A: 

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();
   }
}
Dennis Cheung
Hehe, this is smart. I like it. But still wrong. Check out my proof. It does not rely on anything being changed after first setup. Every non-constant hash-function will go wrong. And of course, immutability does not heal the non-transitivity.
nes1983
A: 

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()".

Dennis Cheung
What does that mean?
nes1983
+2  A: 

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.

mP
A: 

super duh moment. was trying to write hashCode() before validating equals() contract.

thanks to Zach Scrivena for the answer! thanks to every one else for their time and response.

A: 

What if we use TreeMap to replace HashMap. Is it possible to write a valid compareTo() method for MyClass ?