Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inconsistent hashcode and equals java

After researching I still can't find the specific solution for my problem. I have an "approximately equals" method that uses an epsilon, while my hashCode method uses the exact values. This breaks the precondition of HashSet when I compare the values.

@Override
public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof EPoint)) {
        return false;
    }
    EPoint ePoint = (EPoint) o;
    return Math.abs(Math.abs(ePoint.lat) - Math.abs(lat)) < EPSILON && Math.abs(Math.abs(ePoint.lon) - Math.abs(lon)) < EPSILON;
}

@Override
public int hashCode() {
    return Objects.hash(lat, lon);
}

I can't find a way to make the hasCode() consistent with my equals method.

like image 583
Alex Blasco Avatar asked Oct 30 '17 13:10

Alex Blasco


People also ask

Is it mandatory to override hashCode and equals method?

You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object. hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.

When should we override hashCode and equals?

Overriding only equals() method without overriding hashCode() causes the two equal instances to have unequal hash codes, which violates the hashCode contract (mentioned in Javadoc) that clearly says, if two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two ...

What happens if we do not override hashCode () and equals () in HashMap?

If you don't override hashcode() then the default implementation in Object class will be used by collections. This implementation gives different values for different objects, even if they are equal according to the equals() method.


2 Answers

Your equals itself breaks the contract even before you get to hashCode because it isn't transitive.

This also immediately leads to the only consistent hashCode implementation being to return a constant, because for any two points there is a (very long) chain of intermediate points so that

  1. every two neighbors are equal, therefore

  2. every two neighbors must have the same hashCode, therefore

  3. beginning and end must have the same hashCode.

Now, this is a consistent implementation, but quite obviously a useless one.

like image 80
Alexey Romanov Avatar answered Oct 14 '22 05:10

Alexey Romanov


I agree with Kayaman: The way your equals methos is implemented, you can have three EPoints (pointA,pointB,and pointC) with:

pointA.equals(pointB) //true
pointA.equals(pointC) //true
pointB.equals(pointC) //false

And this is not allowed. Creating a method with another name might be a solution.

If, however, you need your "almost Equal" objects to have the same hashcode, you could try a different approach:
Map every EPoint to an EPoint out of a grid. If, e.g. your EPoint's lat and lon where floats, you could map each EPoint to an EPoint with the rounded int-values.
If you need higher precision, you could extend on that and go into first, second,...decimal place).

If you do the equals() and the hashcode() method against the "mapped" Point, this should satisfy all requirements:

@Override
public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof EPoint)) {
        return false;
    }
    EPoint ePoint = (EPoint) o;
    return this.gridLon() == ePoint.gridLon() && ePoint.gridLat() == this.gridLat();
}

@Override
public int hashCode() {
    return Objects.hash(this.gridLon(), this.gridLat());
}
like image 28
Dieter Drobny Avatar answered Oct 14 '22 05:10

Dieter Drobny