views:

907

answers:

7

In Java, I have a subclass Vertex of the Java3D class Point3f. Now Point3f computes equals() based on the values of its coordinates, but for my Vertex class I want to be stricter: two vertices are only equal if they are the same object. So far, so good:

class Vertex extends Point3f {

    // ...

    public boolean equals(Object other) {
        return this == other;
    }
}

I know this violates the contract of equals(), but since I'll only compare vertices to other vertices this is not a problem.

Now, to be able to put vertices into a HashMap, the hashCode() method must return results consistent with equals(). It currently does that, but probably bases its return value on the fields of the Point3f, and therefore will give hash collisions for different Vertex objects with the same coordinates.

Therefore I would like to base the hashCode() on the object's address, instead of computing it from the Vertex's fields. I know that the Object class does this, but I cannot call its hashCode() method because Point3f overrides it.

So, actually my question is twofold:

  • Should I even want such a shallow equals()?
  • If yes, then, how do I get the object's address to compute the hash code from?

Edit: I just thought of something... I could generate a random int value on object creation, and use that for the hash code. Is that a good idea? Why (not)?

A: 

The function hashCode() is inherited from Object and works exactly as you intend (on object level, not coordinate-level). There should be no need to change it.

As for your equals-method, there is no reason to even use it, since you can just do obj1 == obj2 in your code instead of using equals, since it's meant for sorting and similar, where comparing coordinates makes a lot more sense.

Christian P.
I have no control over what comparison the HashMap uses...
Thomas
+10  A: 

Either use System.identityHashCode() or use an IdentityHashMap.

Alex Miller
Actually... that is exactly what I needed :D
Thomas
+1  A: 

System.identityHashCode() returns the same hash code for the given object as would be returned by the default method hashCode(), whether or not the given object's class overrides hashCode().

Bill the Lizard
A: 

You use a delegate even though this answer is probably better.


class Vertex extends Point3f{
   private final Object equalsDelegate = new Object();
   public boolean equals(Object vertex){
      if(vertex instanceof Vertex){
         return this.equalsDelegate.equals(((Vertex)vertex).equalsDelegate);
      }
      else{
         return super.equals(vertex);
      }
   }
   public int hashCode(){
      return this.equalsDelegate.hashCode();
   }
}
Jay R.
A: 

Just FYI, your equals method does NOT violate the equals contract (for the base Object's contract that is)... that is basically the equals method for the base Object method, so if you want identity equals instead of the Vertex equals, that is fine.

As for the hash code, you really don't need to change it, though the accepted answer is a good option and will be a lot more efficient if your hash table contains a lot of vertex keys that have the same values.

The reason you don't need to change it is because it is completely fine that the hash code will return the same value for objects that equals returns false... it is even a valid hash code to just return 0 all the time for EVERY instance. Whether this is efficient for hash tables is completely different issue... you will get a lot more collisions if a lot of your objects have the same hash code (which may be the case if you left hash code alone and had a lot of vertices with the same values).

Please don't accept this as the answer though of course (what you chose is much more practical), I just wanted to give you a little more background info about hash codes and equals ;-)

Mike Stone
It does violate the contract, because Point3f.equals(Vertex) could return true, while the opposite call would always return false. Hence, my implementation is not symmetric.
Thomas
I also realize that the default hashCode() is valid, but it is doing more than necessary. And since I use HashTable and HashSet quite a lot, I figured this would be a performance win.
Thomas
well, it violates the Point3f equals method then, not the Object equals method... which is what I was referring to (and what hash tables are interested in)
Mike Stone
A: 

Why do you want to override hashCode() in the first place? You'd want to do it if you want to work with some other definition of equality. For example

public class A { int id;

public boolean equals(A other) { return other.id==id} public int hashCode() {return id;}

} where you want to be clear that if the id's are the same then the objects are the same, and you override hashcode so that you can't do this:

HashSet hash= new HashSet(); hash.add(new A(1)); hash.add(new A(1)); and get 2 identical(from the point of view of your definition of equality) A's. The correct behavior would then be that you'd only have 1 object in the hash, the second write would overwrite.

Steve B.
What you say is correct, but I fail to see your point.
Thomas
A: 

Since you are not using equals as a logical comparison, but a physical one (i.e. it is the same object), the only way you will guarantee that the hashcode will return a unique value, is to implement a variation of your own suggestion. Instead of generating a random number, use UUID to generate an actual unique value for each object.

The System.identityHashCode() will work, most of the time, but is not guaranteed as the Object.hashCode() method is not guaranteed to return a unique value for every object. I have seen the marginal case happen, and it will probably be dependent on the VM implementation, which is not something you will want your code be dependent on.

Excerpt from the javadocs for Object.hashCode(): As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)

The problem this addresses, is the case of having two separate point objects from overwriting each other when inserted into the hashmap because they both have the same hash. Since there is no logical equals, with the accompanying override of hashCode(), the identityHashCode method can actually cause this scenario to occur. Where the logical case would only replace hash entries for the same logical point, using the system based hash can cause it to occur with any two objects, equality (and even class) is no longer a factor.

Robin
System.identityHashCode() is fine, because hashCode() is not *expected* to return distinct values for distinct objects; it is only required to return equal values for equal objects.
Thomas