Java equals and hashCode Tutorial

Overview

This tutorial shows you what equals and hashCode are and how to properly override them.

If you have written in Java long enough, you will learn that using the equals method instead of == is the right way to assert equality.

This is because the == operator compares the two objects’ memory addresses.

The equals method, as defined in the Object class, uses the == operator too. This is not always what you want.

Override the equals method

Consider this example, we have the Person class with two fields: name and socialId.

class Person {
    private String name;
    private int socialId;
    
    public Person(String name, int socialId) {
        this.name = name;
        this.socialId = socialId;
    }
}

As a convention, people with the same socialId are one. That means if two people with the same socialId should be equal.

However, if I run this code, the result is false:


public class EqualityExample {
    public static void main(String[] args) {
        var jane = new Person("Jane", 1);
        var alsoJane = new Person("Jane(she/her)", 1);

        System.out.println(jane.equals(alsoJane));
    }
}

The reason is quite simple, I didn’t override the equals method inherited from the Object class. Thus, equals here compare two objects using ==.

equals overriding

The IDE (Intellij IDEA) provides a handy helper to generate equals and hashCode methods (I will mention hashCode later). Here is the equals method that takes only socialId into account:

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return socialId == person.socialId;
    }

Let’s walk through the math here:

  1. On line 3, the function check if the two objects are pointing to the same memory address. If this is the case, return true.
  2. The next line (4) checks if the comparing object is null or not. No object should equal null. Thus, if the passing object this null, the method returns false immediately. Next, it will check if the two instances are from the same class. If their classes are not identical, return false.
  3. Finally, on lines 5 and 6, the object o is cast to the Person class and the two objects are compared by their ID (as I specified, I’m going to compare the socialId field only).

Now, let’s run the comparison code again, the result should be true:

public class EqualityExample {
    public static void main(String[] args) {
        var jane = new Person("Jane", 1);
        var alsoJane = new Person("Jane(she/her)", 1);

        System.out.println(jane.equals(alsoJane));//returns true
    }
}


class Person {
    private String name;
    private int socialId;

    public Person(String name, int socialId) {
        this.name = name;
        this.socialId = socialId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return socialId == person.socialId;
    }

}

The hashCode method

Now we are able to tell the code that two people with the same social Id are equal. However, let’s consider this example:

public class EqualityExample {
    public static void main(String[] args) {
        var jane = new Person("Jane", 1);
        var alsoJane = new Person("Jane(she/her)", 1);

        System.out.println(jane.equals(alsoJane));

        var peopleSet = new HashSet<Person>();

        peopleSet.add(jane);
        peopleSet.add(alsoJane);

        System.out.printf("Number of people %%d", peopleSet.size());
    }
}

Running the code above would provide this result:

Before implementing hashCode
Before implementing hashCode

The HashSet implements the Set interface. One characteristic of this interface is not storing duplicate objects. However, as you can see from the result, the set stored 2 instances of Jane.

So, how do the HashSet instance know two objects are duplicates? It uses the hashCode method.

Let’s dive into the add method of the HashSet class:

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

As you can see, HashSet uses a Map to handle its elements. Let’s now go to the put method of the map:

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

This method in turn calls the hash method, which uses hashCode internally:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

Now, if you want to treat two instances with the same social Id as one, you should override the hashCode method.

Intellij IDEA provides an implementation here:

    @Override
    public int hashCode() {
        return Objects.hash(socialId);
    }

Now, if I run the code, I should see the expected result: the set now has only 1 element:

After implementing hashCode
After implementing hashCode

Conclusion

Object equality is an important topic in Java. The default implementation of equals and hashCode may not be suitable in all cases. In such situations, you need to override them to suit to your business logic. Overriding the equals method only may not be enough. You should also override hashCode’s implementation too.

Leave a Comment