HelloKoding

Practical coding guides

equals and hashCode contract in Java

In Java, the equals method is used to compare the equivalence between two objects. hashCode() method is used to generate the object’s hash code which is used to properly distribute entries across hash table based collections such as HashMap or HashSet

By default, both of them are defined in the Object class and inherited by all Java classes. However, you should consider to override them on your classes to make them useful in practice

The equals method of Object class

The equals method of Object class is defined as below

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

It returns true when the calling object this and the parameter obj point to the same memory address

The default equals method problem

In practice, besides comparing memory addresses, you also need the equals() method comparing your objects based on their internal fields

In the following example, book1.equals(book2) should return true but false as BookWithoutEquals’s equals method is derived from Object so it’s only comparing memory addresses of book1 and book2 which always returns false

@Test
public void givenNonEqualsImplement_whenCompareObjects_thenReturnsFalse() {
    BookWithoutEquals book1 = new BookWithoutEquals("A");
    BookWithoutEquals book2 = new BookWithoutEquals("A");

    assertThat(book1.equals(book2)).isFalse();
}

BookWithoutEquals.java

package com.hellokoding.java.lang;

public class BookWithoutEquals {
    String title;

    public BookWithoutEquals(String title) {
        this.title = title;
    }
}

How to implement a custom equals() method

The implementation usually has 3 parts

  • Compares memory addresses of the owner and the parameter object
if (this == o) return true;
  • Checks the nullability of the parameter and compares the class type of the owner and the parameter
if (o == null || getClass() != o.getClass()) return false;
  • Compares object fields by using Java 7+ Objects.equals
return Objects.equals(title, that.title) &&
    Objects.equals(pages, that.pages);

The following gives you implementation example

BookWithEquals.java

package com.hellokoding.java.lang;


import java.util.Objects;

public class BookWithEquals {
    public String title;
    public Integer pages;

    public BookWithEquals(String title, Integer pages) {
        this.title = title;
        this.pages = pages;
    }

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

book1.equals(book2) now returns true as expected

@Test
public void givenEqualsImplement_whenCompareObjects_thenReturnsTrue() {
    BookWithEquals book1 = new BookWithEquals("A", 1);
    BookWithEquals book2 = new BookWithEquals("A", 1);

    assertThat(book1.equals(book2)).isTrue();
}

The hashCode method

The hashCode method generates an object’s hash code value. It is used to properly distribute entries across hash table based collections such as HashMap and HashSet

Learn more about hash table data structure

Lack of equals and hashCode implementation in your classes can cause troubles when they are stored inside a HashMap or HashSet. Let’s take a look at this example

@Test
public void givenEqualsImplement_whenCompareInHashMap_thenReturnsTrue() {
    BookWithEquals bookA = new BookWithEquals("A", 1);
    BookWithEquals bookB = new BookWithEquals("B", 2);
    Map<BookWithEquals, Integer> bookMap = Map.of(bookA, 1, bookB, 2);

    assertThat(bookMap.containsKey(new BookWithEquals("A", 1))).isTrue();
}

The above test case is not successful as containsKey method of HashMap works based on BookWithEquals’s hashCode() and equals() but only equals() method is overridden

Learn more about Objects comparing in HashMap

How to implement a custom hashCode method

Since Java 7, you can delegate the hash code generation to Object.hash function

return Objects.hash(title, pages);

The following gives you an implementation example

BookWithEqualsAndHashCode.java

package com.hellokoding.java.lang;


import java.util.Objects;

public class BookWithEqualsAndHashCode {
    public String title;
    public Integer pages;

    public BookWithEqualsAndHashCode(String title, Integer pages) {
        this.title = title;
        this.pages = pages;
    }

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

    @Override
    public int hashCode() {
        return Objects.hash(title, pages);
    }
}

bookMap.containsKey(new BookWithEqualsAndHashCode("A", 1)) now returns true as expected

@Test
public void givenEqualsAndHashCodeImplement_whenCompareInHashMap_thenReturnsTrue() {
    BookWithEqualsAndHashCode bookA = new BookWithEqualsAndHashCode("A", 1);
    BookWithEqualsAndHashCode bookB = new BookWithEqualsAndHashCode("B", 2);
    Map<BookWithEqualsAndHashCode, Integer> bookMap = Map.of(bookA, 1, bookB, 2);

    assertThat(bookMap.containsKey(new BookWithEqualsAndHashCode("A", 1))).isTrue();
}

General contract of equals and hashCode

  • hashCode() method must consistently return the same integer during an execution of a Java application
  • If two objects are equals according to the equals(Object) method, then their’s hashCode() method must produce the same integer result

Generate equals and hashCode implementation

You can use the following options to generate equals() and hashCode() implementation on your classes

  • Lombok @EqualsAndHashCode or @Data
  • IDE such as IntelliJ or Eclipse

Learn more about Lombok

Conclusion

In this tutorial, we learned about equals() and hashCode() methods in Java, and how to override them. You can find below all the defined test cases

EqualsAndHashCodeTest.java

package com.hellokoding.java.lang;

import org.junit.Test;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

public class EqualsAndHashCodeTest {
    @Test
    public void givenNonEqualsImplement_whenCompareObjects_thenReturnsFalse() {
        BookWithoutEquals book1 = new BookWithoutEquals("A");
        BookWithoutEquals book2 = new BookWithoutEquals("A");

        assertThat(book1.equals(book2)).isFalse();
    }

    @Test
    public void givenEqualsImplement_whenCompareObjects_thenReturnsTrue() {
        BookWithEquals book1 = new BookWithEquals("A", 1);
        BookWithEquals book2 = new BookWithEquals("A", 1);

        assertThat(book1.equals(book2)).isTrue();
    }

    @Test
    public void givenEqualsImplement_whenCompareInHashMap_thenReturnsFalse() {
        BookWithEquals bookA = new BookWithEquals("A", 1);
        BookWithEquals bookB = new BookWithEquals("B", 2);
        Map<BookWithEquals, Integer> bookMap = Map.of(bookA, 1, bookB, 2);

        assertThat(bookMap.containsKey(new BookWithEquals("A", 1))).isFalse();
    }

    @Test
    public void givenEqualsAndHashCodeImplement_whenCompareInHashMap_thenReturnsTrue() {
        BookWithEqualsAndHashCode bookA = new BookWithEqualsAndHashCode("A", 1);
        BookWithEqualsAndHashCode bookB = new BookWithEqualsAndHashCode("B", 2);
        Map<BookWithEqualsAndHashCode, Integer> bookMap = Map.of(bookA, 1, bookB, 2);

        assertThat(bookMap.containsKey(new BookWithEqualsAndHashCode("A", 1))).isTrue();
    }
}
Follow HelloKoding