Skip to content

2019.04.19 Implementing .equals() and .hashCode() in Java

Glen K. Peterson edited this page May 1, 2019 · 29 revisions

This article is a recipe for fulfilling the contract for Object.equals() and .hashCode(). .hashCode() yields an integer for a quick can-equal test, while .equals() tests for exact equality and can be a slower test. Implementing these correctly is what enables O(1) time complexity of hash-based collections and can make unit testing your object graphs much easier.

The following example assumes a class called MyClass with a surrogate key called id and two important fields called field1 and field2:

@Override
public boolean equals(Object other) {
    if (this == other) {
        return true;
    }
    if ( !(other instanceof MyClass) ) {
        return false;
    }

    // Now it's safe to cast.
    final MyClass that = (MyClass) other;

    // If both objects have surrogate keys assigned,
    // just compare them and be done.
    if ( (id != 0) && (that.id != 0) ) {
        return (id == that.id);
    }

    // Otherwise compare the *same* important fields that you use
    // in .hashCode().
    return Objects.equals(field1, that.field1) &&
           Objects.equals(field2, that.field2);
}

@Override
public int hashCode() {
    if (id == 0) {
        // If there's no surrogate key assigned, hash the same
        // important fields you use in .equals()
        return Objects.hash(field1, field2);
    } else {
        // Have a surrogate key?  Just return the low bits.
        return (int) id;
    }
}

Consider:

  • The surrogate key / ID (which uniquely identifies the object to ORM and database) is usually assigned by the ORM/database the first time the object is saved (after object creation, so it's usually not part of the constructor). Once the surrogate key has been assigned, it will be used as the only check for hashCode and equality (it's just a Long, so the check will be very fast).
  • When the id is not defined, we compare the important fields in .equals() and .hashCode().
  • Not every field is important. We generally only care about the minimum number of fields that uniquely identify this object without the surrogate key. The "natural key" for the object in DB terms. Good indicators of importance are if a field is:
    • covered by a unique constraint in the database
    • used for sorting
    • set by the constructor (constructor arguments generally represent what you need to think about when instantiating a class, which should cover uniqueness, but may add other concerns).
    • non-null (unless primitive or a collection)

HashCode

Instead of Objects.hash(), you could write something like:

int ret = 0;
if (field1 != null) {
    ret ^= field1;
}
if (field2 != null) {
    ret ^= field2;
}
return ret;

It might even be marginally faster because no array is created and no function is called. But I would start using Objects.hash() because:

  • It's easy to see if you're comparing the same fields as in your equals method.
  • It's really hard to make any kind of typo.
  • If you're using a database, the time it takes to retrieve the data and convert it from a String to a POJO completely eclipses that time it takes to call Objects.hash(). You can always write it out longhand, or even use bitwise operations if you ever need to, but 99.9% of the time, this will be good enough.
  • Objects.hash() is used often enough that the JVM should keep it cached/inlined for speed.

Equals

if (this == other) is the cheapest comparison possible because it compares memory locations, doesn't even have to load the objects from memory, and if they are equal you're done after doing almost no work.

if ( !(other instanceof MyClass) ) is a fairly cheap way to exclude anything that could not possibly be equal. It also excludes the case where other is null. Use the name of your class in this instanceof comparison whenever practical.

You could technically use the name of an interface instead of your specific class, but it's rarely a good idea. They did that with HashMap and SortedMap inheriting .equals() from AbstractMap. This means that sort-order is ignored for equality. Is that what you want? Sometimes yes, sometimes no. Any time you implement .equals() on a non-final class you are asking for this kind of ambiguity and confusion, so try not to do that!

Having determined the class, you can safely cast to it MyClass that = (MyClass) other; so that we can use that instead of other for all subsequent comparisons.

Objects.equals() compares two objects taking into account whether they are null or not, then calling their .equals() methods if they are both non-null. It returns true if both are null.

If you implement .compareTo() correctly, using the same fields as .equals() and .hashCode(), then instead of a list of Objects.equals() you can return this.compareTo(that) == 0. TestUtils can help ensure that you've implemented compareTo() correctly.

Q&A

Q: What about declaring every field on an object?

A: That's how most tuples work and a lot of other things. For your own code it is generally sufficient (often preferable) to only compare the fields that force the object to be unique. At least start there, until you find a reason to do differently.

Q: What about .compareTo()?

A: You probably want to compare the same fields you use in your .equals() and .hashCode() methods. Only .equals() and .hashCode() technically have to be compatible, but it's really surprising to use different sets of fields for different kinds of comparisons. If you use different fields, implement a separate Comparator to make it clear that they aren't supposed to match.

Q: I don't care about databases / don't like surrogate keys

A: Simply leave out the id field and related ifs.

Q: I do it differently

A: Awesome! It's a good idea to test any variations with something like TestUtils. If it works, please share!

Q: Do the fields you compare have to be final and immutable?

A: If the values of these fields change after storing them in a hash-based collection, your object will no longer work as a key to that collection. If all important fields are final/immutable, there is no opportunity to make an error. If they are changeable, you have to be careful.

Q: If I only put things with surrogate keys in hash-based collections, can I just do the ID part and leave out the rest?

A: Are you going to serialize these objects? Will any framework you use serialize them? Will you compare them in unit tests? Will someone else be tempted to do any of these things? If the answer to any of these questions is yes, then you should follow the examples above. Otherwise, comparing only ID's will work well in carefully controlled circumstances.

Q: If I implement just the ID part, can I replace the rest with System.identityHashCode(this)?

A: Don't use .identityHashCode() in your implementation of .hashCode(). If you're tempted, it probably means you should not implement .equals() or .hashCode() and just let your class inherit them from Object.

Q: You've mentioned your TestUtils project several times. Is this an ad?

A: TestUtils is how I learned how to implement .equals(), .hashCode(), and .compareTo() correctly. Feel free to recommend alternatives. I recommend using something to test that you're implementing these methods correctly. I regularly find errors by writing good tests for these methods.

Q: Isn't there an easier way?

A: Yes, there is.

Clone this wiki locally