equals() and hashCode() in Java

Every class in Java inherits from the Object class. The Object class defines its own methods, making these methods available to every class you create.

equals() and hashcode() are two of these methods. While Object defines default implementations for these methods, you can override these methods in your own class definitions...

public class Person {

    @Override
    public boolean equals(Object obj){
        ...
    }

    @Override
    public int hashCode(){
        ...
    }

}

Why would you ever override these methods? What is the use of these methods? Let's take a closer look at these methods including detailed explanation and examples.

Java equals()

The equals() method compares the equality of two objects. The Object class defines the default implementation for this method..

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

By default, the equals() method returns false if the two objects aren't the same instance...

Object object1 = new Object();
Object object2 = new Object();

object1.equals(object1); //returns true
object1.equals(object2); //returns false

This may seem obvious but sometimes the default behavior is not what you want...

Payment payment1 = new Payment("USD", "50.00");
Payment payment2 = new Payment("USD", "50.00");

payment1.equals(payment2) //returns false

In this case we want two instances of Payment to be considered equal if they have the same amount and currency. We can override the default behavior of equals() like so:

@Override
public boolean equals(Object obj){
    if(obj == null)
        return false;
    if(!(obj instanceof Payment))
        return false;
    if(obj == this)
        return true;
    Payment input = (Payment) obj;
    return (input.currency == this.currency) && (input.amount.equals(this.amount));
}

Now the equals() method will return true if two instances of Payment have the same currency and value.

The equals contract..

Notice the example above adds some extra logic for handling null etc. This is for honoring the contract Java defines for equality. Specifically the contract states that equality is:

  • reflexive - x.equals(x)
  • symmetric - x.equals(y), y.equals(x)
  • transitive - x.equals(y), y.equals(z), x.equals(z)
  • consistent - x.equals(y) will always return same thing
  • x.equals(null) should always return false

Why is it important to maintain this contract? While you technically don't need to it can introduce hard to catch runtime bugs. Especially when considering the relationship with hashCode and equality, abiding by this contract is always a good idea in Java.

Java hashCode()

The hashCode() method returns an integer representing the hash code for an object. You won't care about hash code values unless you're using hash based collections like HashMap, HashSet and HashTable.

This is because an object's hash code value is used to determine the key or bucket a particular value is stored in. For example, let's say you create a HashMap like this...

HashMap map = new HashMap();
map.put(new Payment("USD", "50.00"), "Sam");
map.get(new Payment("USD", "50.00")) //returns "Sam"

HashMap internally uses the hash code value of the Payment instance to determine which bucket the value "Sam" gets stored in.

This may seem overly complicated. After all the unique key is Payment("USD", "50.00") right?

Turns out the hashCode() function is called all the time to internally identify these keys.

The default implementation of hashCode()

If you look at Object's default implementation of hashCode() you'll see..

public native int hashCode();

But where is the actual body or implementation of this?

Remember that the native keyword actually points to implementations in other libraries like C/C++. This means the JVM utilizes lower level libraries to implement how hash codes are generated.

While the implementation varies depending on the JVM, the hashCode() method typically returns integer representations of underlying memory addresses, random number generators, or thread states.

The interworkings of such native methods is outside the scope of this article. Instead it's important to remember that, like equals(), the hashCode() method can be overridden but still must abide by a contract...

The hashCode contract

Specifically, the hashCode() contract states that:

  • the integer returned by an object's hashCode() implementation must be the same during the execution of an application (providing no information used in equals() is modified)
  • the same integer must be returned for two objects considered equal
  • the same integer may be returned for two objects considered unequal

Long story short if two objects are considered equal they must return different hash code values. If two objects are unequal, it is ok if they have the same hash code value (but this is not recommended).

The difference between equals() and hashcode() in Java

While equals() compares equality between two objects, hashCode() returns the hash code integer representation of a given object.

You're already starting to see the relationships between these two methods. The hashCode() contract specifies the same hash code integer should be returned for objects considered to be equal...and the equals() method dictates the equality between two objects.

So while equals() and hashCode() are clearly different they are closely related via their contracts.

Why you need to override hashCode() and equals()

You only need to override hashCode() when you override equals(). This is because of the contracts between the two methods.

Remember that hashCode() must return the same value for "equal objects". If you override equals() then you have to override hashCode() so this fact holds true...

public class Payment {
    private String currency;
    private Double amount;

    public Payment(String currency, Double amount){
        this.currency = currency;
        this.amount = amount;
    }

    @Override
    public final int hashCode(){
        int hashVal = 1;
        if(this.amount != null){
            hashVal += this.amount.hashCode();
        }
        if(this.currency != null){
            hashVal += this.currency.hashCode();
        }
        return hashVal;
    }

    @Override
    public boolean equals(Object obj){
        if(obj == null)
            return false;
        if(!(obj instanceof Payment))
            return false;
        if(obj == this)
            return true;
        Payment input = (Payment) obj;
        return (input.currency == this.currency) && (input.amount.equals(this.amount));
    }
}

Notice how we override the equals() method with our own implementation. This is because we want to consider instances of Payment equal as value objects. A payment with the same currency and amount should be considered equal...

To achieve this, we override the equals() method to compare objects based on their currency and amount values.

Since we override equals() we also have to override hashCode() to maintain the contract. Notice how we involve the same fields (currency and amount) in this calculation.

By overriding both methods, the following works as expected...

HashMap map = new HashMap();
map.put(new Payment("USD", 43.00), "Sam");
map.get(new Payment("USD", 43.00)); //returns "Sam" as expected

What happens when you don't override hashCode()...

What if we only override equals() but not hashCode()? While we could get away with this for many use cases, things would quickly turn sour if our Payment class is used with any of the hash based collections...

HashMap map = new HashMap();
map.put(new Payment("USD", 43.00), "Sam");
map.get(new Payment("USD", 43.00)); //returns null!

Notice how null is returned when we would expect "Sam" to be returned given the keys are "equal".

This happens because the default implementation of hashCode() will return a different integer for each instance created. While we've programmed two instances of Payment to be equal based on their currency and amount, the default hashCode implementation still considers them to be different.

Java equals() example

You've already seen how you can override the default equals() method...

@Override
public boolean equals(Object obj){
    if(obj == null)
        return false;
    if(!(obj instanceof Payment))
        return false;
    if(obj == this)
        return true;
    Payment input = (Payment) obj;
    return (input.currency == this.currency) && (input.amount.equals(this.amount));
}

Remember that the override implementation MUST abide by the same contract. This is why you see things like object == null being accounted for in the logic.

Java hashcode() example

You've already seen how you can override the default hashCode() method...

@Override
public final int hashCode(){
    int hashVal = 1;
    if(this.amount != null){
        hashVal += this.amount.hashCode();
    }
    if(this.currency != null){
        hashVal += this.currency.hashCode();
    }
    return hashVal;
}

Remember that when you override hashCode() you must reference the same information used in the custom equals() implementation (in this case amount and currency).

Conclusion

You only need to override hashCode() if you override equals(). You typically only need to override equals() if you want to compare value objects. Things like payments, currency, or other classes that would intuitively equal one another with the same information should have custom equals() implementations.

Remember that hashCode() is only relevant to hash based collections. While you could technically get away with not overriding this method if your class isn't utilized in hash collections, it's considered best practice to account for this when overriding equals().

Your thoughts?