Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling hashCode() from equals()

I have defined hashCode() for my class, with a lengthy list of class attributes.

Per the contract, I also need to implement equals(), but is it possible to implement it simply by comparing hashCode() inside, to avoid all the extra code? Are there any dangers of doing so?

e.g.

@Override
public int hashCode() 
{
    return new HashCodeBuilder(17, 37)
        .append(field1)
        .append(field2)
    // etc.
    // ...
}

@Override
public boolean equals(Object that) {
    // Quick special cases
    if (that == null) {
        return false;
    }
    if (this == that) {
        return true;
    }
    // Now consider all main cases via hashCode()
    return (this.hashCode() == that.hashCode());
}
like image 227
gene b. Avatar asked Apr 27 '15 18:04

gene b.


1 Answers

Don't do that.

The contract for hashCode() says that two objects that are equal must have the same hashcode. It doesn't guarantee anything for objects that are not equal. What this means is that you could have two objects that are completely different but, by chance, happen to have the same hashcode, thus breaking your equals().

It is not hard to get hashcode collisions between strings. Consider the core loop from the JDK 8 String.hashCode() implementation:

for (int i = 0; i < value.length; i++) {
    h = 31 * h + val[i];
}

Where the initial value for h is 0 and val[i] is the numerical value for the character in the ith position in the given string. If we take, for example, a string of length 3, this loop can be written as:

h = 31 * (31 * val[0] + val[1]) + val[2];

If we choose an arbitrary string, such as "abZ", we have:

h("abZ") = 31 * (31 * 'a' + 'b') + 'Z'
h("abZ") = 31 * (31 * 97 + 98) + 90
h("abZ") = 96345

Then we can subtract 1 from val[1] while adding 31 to val[2], which gives us the string "aay":

h("aay") = 31 * (31 * 'a' + 'a') + 'y'
h("aay") = 31 * (31 * 97 + 97) + 121
h("aay") = 96345

Resulting in a collision: h("abZ") == h("aay") == 96345.

Also, note that your equals() implementation does not check if you are comparing objects of the same type. So, supposing you had this.hashCode() == 96345, the following statement would return true:

yourObject.equals(Integer.valueOf(96345))

Which is probably not what you want.

like image 110
Anderson Vieira Avatar answered Sep 28 '22 01:09

Anderson Vieira