# Understanding equals() and hashCode() Methods in Java and Their Role in Collections

In Java, the `equals()` and `hashCode()` methods are key for comparing objects and storing them in collections. Though they may look simple, implementing them correctly is crucial for the accuracy, performance, and reliability of Java applications, especially with collections like `HashSet`, `HashMap`, and `LinkedHashSet`.

This article will explore the following:

1. **What are** `equals()` and `hashCode()`?
    
2. **Why are they important?**
    
3. **How are they used in Java collections?**
    
4. **Best practices for overriding these methods.**
    
5. **The role of the** `@EqualsAndHashCode` annotation in Lombok and how it simplifies the process.
    

By the end, you'll understand how these methods work, how to implement them correctly, and how to use Lombok to make your Java code simpler.

### 1\. What are `equals()` and `hashCode()`?

#### The `equals()` Method

The `equals()` method in Java is defined in the `Object` class, which means that every Java class inherits it. Its purpose is to compare two objects for equality.

The default implementation of `equals()` checks for reference equality, meaning two objects are considered equal if they reference the same memory address. This is often not the desired behavior, especially when you want to compare the content of objects rather than their memory locations.

Here's the default implementation from the `Object` class:

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

In most cases, you'll want to override this method in your custom classes to compare object states (i.e., their fields).

#### The `hashCode()` Method

The `hashCode()` method also comes from the `Object` class. It returns an integer value (the "hash code") associated with the object. The purpose of this method is to allow objects to be efficiently stored and retrieved in hash-based collections like `HashMap` and `HashSet`.

The general contract of `hashCode()` is:

* If two objects are equal according to the `equals()` method, they must have the same hash code.
    
* However, two objects having the same hash code doesn't necessarily mean they are equal.
    

The default implementation of `hashCode()` in the `Object` class returns a unique integer based on the object's memory address:

```java
public int hashCode();
```

### 2\. Why Are `equals()` and `hashCode()` Important?

These two methods are critical when storing objects in collections like `HashMap`, `HashSet`, or any other collection that uses hashing.

#### Hash-based Collections: How Do They Work?

Hash-based collections, such as `HashMap` and `HashSet`, rely on `hashCode()` and `equals()` to store and retrieve objects. When an object is added to a hash-based collection, the collection uses the object's hash code to determine the "bucket" where the object should be placed. If another object is added and it has the same hash code, the collection uses `equals()` to determine whether the two objects are truly equal or if they are distinct entries.

Here’s a breakdown of how this works:

1. **Storing an Object:** When you store an object in a `HashMap` or `HashSet`, the collection computes the hash code of the object (via the `hashCode()` method). It then uses this value to determine the bucket in which to place the object.
    
2. **Retrieving an Object:** When you search for an object, the collection computes the hash code of the object you're looking for and checks the appropriate bucket. If multiple objects have the same hash code, it uses `equals()` to differentiate between them.
    

#### Common Issues

If `equals()` and `hashCode()` are not implemented correctly, you may encounter several issues:

* **Duplicate Entries:** Objects that are logically equal may be considered different, resulting in duplicates in collections like `HashSet`.
    
* **Retrieval Issues:** If the hash code is not consistent with `equals()`, objects might not be retrieved correctly from collections.
    

### 3\. How Are `equals()` and `hashCode()` Used in Java Collections?

#### The Role of `equals()` and `hashCode()` in `HashSet`

A `HashSet` uses a combination of `hashCode()` and `equals()` to ensure that no two equal objects can exist in the set.

* **Inserting an Object:** When adding an object to the `HashSet`, the collection computes the object's hash code to determine which bucket to place it in. If another object is already in that bucket, `equals()` is used to check if the two objects are equal. If they are, the new object is not added.
    
* **Checking for Existence:** When checking whether an object exists in a `HashSet`, the `hashCode()` of the object is computed to find the right bucket. Then, `equals()` is used to check if any object in the bucket is equal to the searched object.
    

#### The Role of `equals()` and `hashCode()` in `HashMap`

Similarly, `HashMap` also relies heavily on `equals()` and `hashCode()` for managing key-value pairs.

* **Inserting a Key-Value Pair:** When a key-value pair is inserted, the hash code of the key is computed, and the key is stored in the corresponding bucket. If another key exists in that bucket, `equals()` is used to check if the keys are equal. If they are, the value is updated.
    
* **Retrieving a Value:** When retrieving a value by key, the hash code of the key is used to find the appropriate bucket, and `equals()` is used to locate the correct key-value pair.
    

#### The Role of `equals()` and `hashCode()` in Other Collections

Collections like `LinkedHashSet`, `TreeSet`, and `ConcurrentHashMap` also rely on `equals()` and `hashCode()`, but some of them have additional ordering constraints or concurrency mechanisms.

### 4\. Best Practices for Overriding `equals()` and `hashCode()`

#### General Guidelines

When overriding `equals()` and `hashCode()`, it’s important to follow certain rules to ensure the integrity of your objects in collections.

1. **Consistency Between** `equals()` and `hashCode()`:
    
    * If two objects are considered equal by `equals()`, they must have the same hash code.
        
    * It’s not necessary for two objects with the same hash code to be equal, but it’s a common best practice to minimize the chances of hash code collisions.
        
2. **Symmetry in** `equals()`:
    
    * If `x.equals(y)` is `true`, then `y.equals(x)` must also be `true`.
        
3. **Reflexivity in** `equals()`:
    
    * An object must always be equal to itself: `x.equals(x)` should always return `true`.
        
4. **Transitivity in** `equals()`:
    
    * If `x.equals(y)` is `true` and `y.equals(z)` is `true`, then `x.equals(z)` should also be `true`.
        
5. **Consistent Hash Codes:**
    
    * The `hashCode()` value should be consistent as long as the object's state <mark>remains unchanged</mark>.
        
    * Avoid using mutable fields in `equals()` and `hashCode()` calculations, as changes to these fields can break collection behavior.
        

#### Implementing `equals()` and `hashCode()`

Here’s an example of a simple class `Person` and how to properly override `equals()` and `hashCode()`:

```java
public class Person {
    private String name;
    private int age;

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

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

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

In this example:

* `equals()` checks both the name and age of the `Person` objects.
    
* `hashCode()` generates a hash code using `Objects.hash()`, which ensures that two equal objects will have the same hash code.
    

#### Handling Null Fields

When overriding `equals()` and `hashCode()`, always be careful with null fields. Using `Objects.equals()` in `equals()` and `Objects.hash()` in `hashCode()` helps avoid `NullPointerException`.

### 5\. Simplifying with Lombok’s `@EqualsAndHashCode`

Lombok is a popular Java library that helps reduce boilerplate code. It automatically generates code for common tasks like `getters`, `setters`, `equals()`, `hashCode()`, and more through annotations.

Lombok’s `@EqualsAndHashCode` annotation generates the `equals()` and `hashCode()` methods for you, following the best practices mentioned earlier. This is extremely useful for reducing code redundancy, especially in classes where `equals()` and `hashCode()` would otherwise be manually implemented.

Here’s how you can use Lombok to simplify the `Person` class:

```java
import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class Person {
    private String name;
    private int age;

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

With `@EqualsAndHashCode`, Lombok generates the `equals()` and `hashCode()` methods based on all fields of the class by default. It automatically ensures that two equal objects have the same hash code, and avoids null issues by internally using `Objects.equals()` and `Objects.hash()`.

#### Customizing `@EqualsAndHashCode`

You can customize which fields are used for the `equals()` and `hashCode()` methods by specifying them in the annotation:

```java
import lombok.EqualsAndHashCode;

@EqualsAndHashCode(of = {"name"})
public class Person {
    private String name;
    private int age;

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

In this example, Lombok will only use the `name` field for `equals()` and `hashCode()`, ignoring the `age` field.

You can also include `callSuper = true` in the annotation to include fields from the superclass in the comparison:

```java
@EqualsAndHashCode(callSuper = true)
public class Employee extends Person {
    private String employeeId;

    public Employee(String name, int age, String employeeId) {
        super(name, age);
        this.employeeId = employeeId;
    }
}
```

This ensures that the fields from both `Person` and `Employee` are used in `equals()` and `hashCode()`.

### Conclusion

The `equals()` and `hashCode()` methods are fundamental in Java for comparing objects and ensuring correct behavior in hash-based collections like `HashMap` and `HashSet`. Correctly overriding these methods is crucial for avoiding issues like duplicate objects or incorrect retrievals from collections.

Best practices include ensuring consistency between `equals()` and `hashCode()`, adhering to symmetry, reflexivity, and transitivity in `equals()`, and handling null values carefully. Using `Objects.equals()` and `Objects.hash()` can simplify implementation and avoid common pitfalls.

For developers seeking to reduce boilerplate code, Lombok’s `@EqualsAndHashCode` annotation is a powerful tool. It automatically generates `equals()` and `hashCode()` methods, allowing for customization and ensuring that your code remains clean and maintainable.

In conclusion, mastering `equals()` and `hashCode()` is essential for any Java developer working with collections, and tools like Lombok can greatly enhance productivity while ensuring correctness.
