What is 'hashCode()' in Java?

In the world of Java programming, the hashCode() method is a fundamental concept. It plays a pivotal role in hash-based data structures such as HashMaps and HashSets, ensuring efficient storage and retrieval of data. In this blog post, we’ll delve into the hashCode() method, exploring what it is and how it works. We’ll also demonstrate its application in HashMaps and HashSets. To facilitate your understanding, we’ll create a Maven project and construct a JUnit test. This test will showcase the power of hashCode().

Prerequisites

If you don’t already have Maven installed, you can download it from the official Maven website https://maven.apache.org/download.cgi or through SDKMAN https://sdkman.io/sdks#maven

You can clone the https://github.com/dmakariev/examples repository.

git clone https://github.com/dmakariev/examples.git
cd examples/java-core/hashcode

Creating a Maven Project

Let’s create a our project

  1. Open your terminal and navigate to the directory where you want to create your project.
  2. Run the following command to generate a new Maven project:
    mvn archetype:generate -DgroupId=com.makariev.examples.core -DartifactId=hashcode -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false 
    

    This command generates a basic Maven project structure with a sample Java class, and the group ID and artifact ID are set as per your requirements.

Deleting Initial Files and Updating Dependencies

To clean up the initial files generated by the Maven archetype and update dependencies, follow these steps:

  1. Delete the src/main/java/com/makariev/examples/core/App.java file.
  2. Delete the src/test/java/com/makariev/examples/core/AppTest.java file.
  3. Open the pom.xml file and delete the JUnit 3 dependency (junit:junit).
  4. Add the JUnit 5 and AssertJ dependencies to the pom.xml file:
<dependencies>
    <!-- JUnit 5 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.10.0</version> <!-- Use the latest version -->
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.10.0</version> <!-- Use the latest version -->
        <scope>test</scope>
    </dependency>
    <!-- AssertJ -->
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-core</artifactId>
        <version>3.24.2</version> <!-- Use the latest version -->
        <scope>test</scope>
    </dependency>
</dependencies>

Understanding hashCode() with Examples

Here is how hashCode() is defined in java 8 https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode–

Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap. The general contract of hashCode is:

  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
  • 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.
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the Java™ programming language.)

1. Default hashCode() Method

the default hashCode() is returning value related to the internal address of the object. It is instance based and does not depend on the actual values in the object.

static class ExampleObject {
    private String data;

    // Default `hashCode()` method

    ExampleObject(String data) {
        this.data = data;
    }
}

ExampleObject obj1 = new ExampleObject("example");
ExampleObject obj2 = new ExampleObject("example");
boolean result = obj1.hashCode()==obj2.hashCode(); //false

2. Custom hashCode() Method

class CustomHashCodeObject {
    private String data;

    CustomHashCodeObject(String data) {
        this.data = data;
    }

    @Override
    public int hashCode() {
        return data.hashCode();
    }
}

CustomHashCodeObject obj1 = new CustomHashCodeObject("example");
CustomHashCodeObject obj2 = new CustomHashCodeObject("example");
boolean result = obj1.hashCode()==obj2.hashCode(); //true

3. hashCode() for Strings

String str = "example";
int hash = str.hashCode();

4. Custom hashCode() for Complex Objects

class ComplexObject {
    private int id;
    private String name;

    ComplexObject(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

ComplexObject obj = new ComplexObject(42, "example");
int hash = obj.hashCode();

5. hashCode() for Enums

enum Color {
    RED, GREEN, BLUE
}

Color color = Color.RED;
int hash = color.hashCode();

Using hashCode()in HashMaps and HashSets

6. HashMap with Custom hashCode()

import java.util.HashMap;

class CustomHashCodeObject {
    private String data;

    CustomHashCodeObject(String data) {
        this.data = data;
    }

    @Override
    public int hashCode() {
        return data.hashCode();
    }
}

HashMap<CustomHashCodeObject, String> hashMap = new HashMap<>();
CustomHashCodeObject obj = new CustomHashCodeObject("example");
hashMap.put(obj, "Value");
String value = hashMap.get(obj);

7. HashSet with Custom hashCode()

import java.util.HashSet;

class CustomHashCodeObject {
    private String data;

    CustomHashCodeObject(String data) {
        this.data = data;
    }

    @Override
    public int hashCode() {
        return data.hashCode();
    }
}

HashSet<CustomHashCodeObject> hashSet = new HashSet<>();
CustomHashCodeObject obj = new CustomHashCodeObject("example");
hashSet.add(obj);
boolean contains = hashSet.contains(obj);

8. HashMap Collision Handling

class CustomHashCodeObject {
    private String data;

    CustomHashCodeObject(String data) {
        this.data = data;
    }

    @Override
    public int hashCode() {
        return 42; // Constant hash code
    }
}

HashMap<CustomHashCodeObject, String> collisionHashMap = new HashMap<>();
CustomHashCodeObject obj1 = new CustomHashCodeObject("example1");
CustomHashCodeObject obj2 = new CustomHashCodeObject("example2");
collisionHashMap.put(obj1, "Value1");
collisionHashMap.put(obj2, "Value2");
String value1 = collisionHashMap.get(obj1);
String value2 = collisionHashMap.get(obj2);

9. HashSet with Collisions

class CustomHashCodeObject {
    private String data;

    CustomHashCodeObject(String data) {
        this.data = data;
    }

    @Override
    public int hashCode() {
        return 42; // Constant hash code
    }
}

HashSet<CustomHashCodeObject> collisionHashSet = new HashSet<>();
CustomHashCodeObject obj1 = new CustomHashCodeObject("example1");
CustomHashCodeObject obj2 = new CustomHashCodeObject("example2");
collisionHashSet.add(obj1);
collisionHashSet.add(obj2);
boolean contains1 = collisionHashSet.contains(obj1);
boolean contains2 = collisionHashSet.contains(obj2);

JUnit 5 Test - HashCodeExampleTest

Now, let’s create a single JUnit 5 test called HashCodeExampleTest.java in the src/test/java/com/makariev/examples/core directory to demonstrate these examples.

package com.makariev.examples.core;

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.HashMap;
import java.util.HashSet;

public class HashCodeExampleTest {

    @Test
    void testHashCode() {
        // Example 1: Default `hashCode()` Method
        ExampleObject obj1 = new ExampleObject();
        ExampleObject obj2 = new ExampleObject();
        assertThat(obj1.hashCode()).isEqualTo(obj2.hashCode());

        // Example 2: Custom `hashCode()` Method
        CustomHashCodeObject customObj1 = new CustomHashCodeObject("example");
        CustomHashCodeObject customObj2 = new CustomHashCodeObject("example");
        assertThat(customObj1.hashCode()).isEqualTo(customObj2.hashCode());

        // Example 3: `hashCode()` for Strings
        String str1 = "example";
        String str2 = "example";
        assertThat(str1.hashCode()).isEqualTo(str2.hashCode());

        // Example 4: Custom `hashCode()` for Complex Objects
        ComplexObject complexObj1 = new ComplexObject(42, "example");
        ComplexObject complexObj2 = new ComplexObject(42, "example");
        assertThat(complexObj1.hashCode()).isEqualTo(complexObj2.hashCode());

        // Example 5: `hashCode()` for Enums
        Color color1 = Color.RED;
        Color color2 = Color.RED;
        assertThat(color1.hashCode()).isEqualTo(color2.hashCode());

        // Example 6: HashMap with Custom `hashCode()`
        HashMap<CustomHashCodeObject, String> hashMap = new HashMap<>();
        CustomHashCodeObject obj = new CustomHashCodeObject("example");
        hashMap.put(obj, "Value");
        String value = hashMap.get(obj);
        assertThat(value).isEqualTo("Value");

        // Example 7: HashSet with Custom `hashCode()`
        HashSet<CustomHashCodeObject> hashSet = new HashSet<>();
        CustomHashCodeObject setObj = new CustomHashCodeObject("example");
        hashSet.add(setObj);
        boolean contains = hashSet.contains(setObj);
        assertThat(contains).isTrue();

        // Example 8: HashMap Collision Handling
        HashMap<CustomHashCodeObject, String> collisionHashMap = new HashMap<>();
        CustomHashCodeObject collisionObj1 = new CustomHashCodeObject("example1");
        CustomHashCodeObject collisionObj2 = new CustomHashCodeObject("example2");
        collisionHashMap.put(collisionObj1, "Value1");
        collisionHashMap.put(collisionObj2, "Value2");
        String collisionValue1 = collisionHashMap.get(collisionObj1);
        String collisionValue2 = collisionHashMap.get(collisionObj2);
        assertThat(collisionValue1).isEqualTo("Value1");
        assertThat(collisionValue2).isEqualTo("Value2");

        // Example 9: HashSet with Collisions
        HashSet<CustomHashCodeObject> collisionHashSet = new HashSet<>();
        CustomHashCodeObject collisionObjA = new CustomHashCodeObject("example1");
        CustomHashCodeObject collisionObjB = new CustomHashCodeObject("example2");
        collisionHashSet.add(collisionObjA);
        collisionHashSet.add(collisionObjB);
        boolean collisionContains1 = collisionHashSet.contains(collisionObjA);
        boolean collisionContains2 = collisionHashSet.contains(collisionObjB);
        assertThat(collisionContains1).isTrue();
        assertThat(collisionContains2).isTrue();
    }

    // Definition for ComplexObject and Color here...
}

Running the Test

To run the test, execute the following command in the project’s root directory:

mvn test

JUnit 5 and AssertJ will execute the test, and you should see output indicating whether the test passed or failed.

Conclusion

In this blog post, we’ve delved into the hashCode() method in Java, explaining what it is and how it works. We’ve provided examples showcasing the behavior of hashCode() and demonstrating its application in HashMaps and HashSets. Understanding hashCode() is crucial for working with hash-based data structures, ensuring efficient data storage and retrieval.


Coffee Time!

Happy coding!

Share: Twitter LinkedIn