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
- Creating a Maven Project
- Deleting Initial Files and Updating Dependencies
- Understanding
hashCode()
with Examples - Using
hashCode()
in HashMaps and HashSets - JUnit 5 Test - HashCodeExampleTest
- Conclusion
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
- Open your terminal and navigate to the directory where you want to create your project.
- 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:
- Delete the
src/main/java/com/makariev/examples/core/App.java
file. - Delete the
src/test/java/com/makariev/examples/core/AppTest.java
file. - Open the
pom.xml
file and delete the JUnit 3 dependency (junit:junit). - 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 thehashCode
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 thehashCode
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.
Happy coding!