1. Introduction

In this tutorial, we're going to look at the different ways to generate the equals and hashCode methods in Java.

2. Default Implementations

Before writing custom equals and hashCode implementations, let's first look at the default behavior.

The equals method as defined in java.lang.Object checks the object identity. In other words, it returns true if two object references point to the same object in the heap.

In compliance with the equals method, the hashCode method returns an integer representing the object's memory address. Thus every instance returns a different hash code.

To implement logical equality, we must override equals and hashCode, and compare the instance fields instead of memory addresses. One important note is that we must override equals and hashCode at the same time. As the general contract of hashCode states, if two objects are equal, the hashCode method must return the same value for each of them.

3. Using JDK

We'll start with the java.util.Objects class that comes with the JDK.

To generate equals, we'll use the Objects.equals method. Similarly to generate hashCode, we'll make use of the Objects.hash method:

public class PersonUsingJdk {

    private String firstName;
    private String lastName;

    // Getters and setters...

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        PersonUsingJdk that = (PersonUsingJdk) o;
        return Objects.equals(firstName, that.firstName) &&
          Objects.equals(lastName, that.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName);
    }
}

Here, in the equals method, we're first checking the object identity, and then the class equality. After these checks, we're calling Objects.equals for each field that designate equality - firstName and lastName. Then in the hashCode method, we're passing the same set of fields - firstName and lastName - to Objects.hash in order to generate the hash code.

4. Using Apache Commons

Next, we'll examine the Apache Commons Lang library. It provides the EqualsBuilder and HashCodeBuilder classes to generate the equals and hashCode methods.

public class PersonUsingCommons {

    private String firstName;
    private String lastName;

    // Getters and setters...

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        PersonUsingCommons that = (PersonUsingCommons) o;

        return new EqualsBuilder()
          .append(firstName, that.firstName)
          .append(lastName, that.lastName)
          .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
          .append(firstName)
          .append(lastName)
          .toHashCode();
    }
}

In the equals method,  we're first checking the object identity and class equality - similar to the previous Objects.equals example. Then we're creating an instance of EqualsBuilder and adding the firstName and lastName fields to be compared during an equality check.

The hashCode method creates an instance of HashCodeBuilder and uses the same fields - firstName and lastName - for the hash code generation.

5. Using Guava

Now let's look at the Guava's Objects class. Its usage is similar to the java.util.Objects class:

public class PersonUsingGuava {

    private String firstName;
    private String lastName;

    // Getters and setters...

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        PersonUsingGuava that = (PersonUsingGuava) o;
        return Objects.equal(firstName, that.firstName) &&
          Objects.equal(lastName, that.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(firstName, lastName);
    }
}

Here, Objects.equal compares a specific field in two objects - i.e. Objects.equal(firstName, that.firstName). On the other hand, Objects.hashCode accepts all selected fields to generate the hash code - i.e. Objects.hashCode(firstName, lastName).

6. Using Lombok

Lastly, we'll look at the Lombok @EqualsAndHashCode annotation as a way of generating equals and hashCode

We'll annotate our class with @EqualsAndHashCode:

@EqualsAndHashCode
public class PersonUsingLombok {

    private String firstName;
    private String lastName;

    // Getters and setters...
}

As a result, Lombok generates the equals and hashCode methods using the instance fields declared in PersonUsingLombok.

Note that the generated methods don't call the equals and hashCode methods of the superclass. In order to call super, we must set the callSuper attribute as true.

@EqualsAndHashCode(callSuper = true)
public class PersonWithInheritance {

7. Summary

In this tutorial, we've examined different ways to generate the equals and hashCode methods.

The java.util.Objects class provides a good dependency-free solution. Guava's Objects class has the equivalent functionality and can be skipped in favor of java.util.Objects.

The EqualsBuilder and HashCodeBuilder classes from Apache Commons Lang are tailored for these operations.

Lastly, Lombok's @EqualsAndHashCode offers generation capabilities at a higher level.

As always, the source code for all examples is available on Github.