Well, as of this writing anyway.

If you've got domain objects that you plan to compare with one another, then you'd better override equals() and hashCode() in your classes.

Otherwise, you could be in for some unpredictable results.

Fortunately, you can override both of them fairly easily. In fact, you might even get away with letting your IDE do it for you.

In this guide, I'll go over equals() and hashCode() so you know what's going on.

The Great Equalizer

You get the equals() method for free. It's part of the base Object class.

But just because you get it for free, that doesn't mean it behaves the way you want it to.

In fact, it probably doesn't behave the way you want it to.

Consider this code that uses the ActivityOutcome class from a CRM application.

ActivityOutcome outcome1 = new ActivityOutcome();
outcome1.setId("1");
outcome1.setName("Interested");

ActivityOutcome outcome2 = new ActivityOutcome();
outcome2.setId("1");
outcome2.setName("Interested");

System.err.println(outcome1.equals(outcome2));

Run that code and, unless you already overrode equals() in the class, it will print out the word "false" in a nice shade of red.

So what gives? The names and the IDs are the same!

Here's what gives: those are two different objects. As you can see from the code above you instantitiated ActivityOutcome twice.

The equals() method in Object checks for object identity. In other words, it will only return true if the two objects are the same instance.

So you need to override equals() if you want that method to check the ID or the name.

But There Are Rules

There are rules for policemen and there are rules for overriding equals(). That equivalance relationship must be:

  • Reflexive - An object must always equal itself
  • Symmetric - If object A equals object B then object B must equal object A
  • Transitive - If object A equals object B and object B equals object C then object A must equal object C
  • Consistent - If object A equals object B then object A must always equals object B unless the data is modified

Also, any check against a null reference must always return false.

If you think about, though, those are some fairly common-sense rules. You probably didn't want to break any of them to begin with.

So with all of that in mind, add this bit to the ActivityOutcome class and you should be gold:

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    ActivityOutcome other = (ActivityOutcome) obj;
    if (id == null) {
        if (other.id != null)
            return false;
    } else if (!id.equals(other.id))
        return false;
    return true;
}

That's all crunched together because I let Eclipse auto-generate it. You can do the same, if you're using that IDE.

But if you just follow the code, you'll find that it's doing exactly what you think it should do to determine if the two objects are equal.

First, it tests for equality with ==. If that returns true, then the two objects are the same and the method returns true.

Next, it checks to see if the other object is null. If it is, then the method returns false.

After that it checks to see if the other object is an instance of the same class. If not, then it returns false.

But if that's true, then it casts the object to ActivityOutcome.

That last block of code checks to see if the two IDs of the objects are the same. If they are, then equals() returns true.

Now if you rerun that first block of code above, you'll see "true" printed out.

But you're not done.

Hashing It Out

Go back to the JavaDoc for Object and take a look at the writeup on the hashCode() method. You might notice this line:

"If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result."

Whoops! You didn't yet take care of that part.

Bottom line: when you override equals(), you gotta override hashCode().

So by now you might be asking: "What the #*&$*@ is a hash code and why does Java need it?"

It's a good question.

But to answer that question, I first need to explain the concept of hashing.

Hashing it comp-sci geek speak for applying a mathemtical function to data. The end result is a code. 

That code could be a hexadecimal number. But here it's an integer.

What's the point of hashing? For efficient retrieval of stored objects.

You've most likely already used hashing if you done Java development for even a short period of time. Does the class name HashMap ring any bells?

A HashMap instance uses hash codes to store and retrieve objects.

But why? Because if it didn't do that then HashMap performance would take a hit.

So the important takeaway here is this: hash codes make your applications run more efficiently and quickly.

So yeah, you need to override hashCode(). For that ActivityOutcome class, override it like this:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    return result;
}

So if you parse that code above you'll find that it's creating a new hash code from the hash code of the id property. It does that with the aid of the prime number 31.

Why does it use that number? It's more high-level math stuff but the bottom line here is that 31 greatly reduces the chances of generating the same hash code between two unequal objects.

By the way, that code was also automagically generated by Eclipse.

You can also use third-party tools like Lombok and Apache Commons to generate hash codes.

Wrapping It Up

There you have it. A coffee-table guide to equals() and hashCode().

Now take what you've learned here to your own domain objects. You'll be glad you did when you start storing them in maps.

But above all else, make sure you have fun!

Photo by Rachel Claire from Pexels