DEV Community

Jay D
Jay D

Posted on

Building Immutable Classes in Java: A Developer’s Guide with a Real-Life Example

You’ve probably heard about immutable classes. But have you ever wondered why they’re so important and how to build your own custom immutable class? In this, we’ll explore immutability in Java with a real-life user profile example to help you understand and apply this concept effectively.

What Is an Immutable Class?
An immutable class is a class whose instances cannot be modified after they are created. Once you create an object, its state stays the same forever.

In Java, popular examples of immutable classes include String, Integer, and other wrapper classes.

Why Are Immutable Classes Useful?
1.Thread-Safety: Since immutable objects can’t be changed, they’re naturally thread-safe. No synchronization is required.
2.Ease of Use: Immutable objects are simple to use and share across methods or threads without worrying about unexpected changes.
3.Predictable Behavior: You know the state of an immutable object will never change, which makes debugging easier.

Real-World Example: Immutable User Profile

Imagine a user profile in an application. When a user creates a profile, their username, email, and date of birth are fixed. If they need to update any detail (e.g., change their email), the system creates a new profile instead of modifying the existing one.

Implementing an Immutable User Class


import java.util.Date;

public final class User {
    private final String username;
    private final String email;
    private final Date dateOfBirth;

    // Constructor to initialize all fields
    public User(String username, String email, Date dateOfBirth) {
        this.username = username;
        this.email = email;
        // Defensively copy mutable fields like Date
        this.dateOfBirth = new Date(dateOfBirth.getTime());
    }

    // Getter for username
    public String getUsername() {
        return username;
    }

    // Getter for email
    public String getEmail() {
        return email;
    }

    // Getter for date of birth (return a copy of the date to ensure immutability)
    public Date getDateOfBirth() {
        return new Date(dateOfBirth.getTime());
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", email='" + email + '\'' +
                ", dateOfBirth=" + dateOfBirth +
                '}';
    }
}
Enter fullscreen mode Exit fullscreen mode

Why Is This Class Immutable?
1.final Class: The User class is declared as final, so no subclass can modify its behavior.
2.Private, Final Fields: All fields (username, email, and dateOfBirth) are private and final, ensuring they are initialized only once.
3.No Setters: The class does not have any setter methods to modify its fields.
4.Defensive Copying: The Date field is mutable, so:
• A new Date object is created in the constructor.
• A copy of the Date is returned in the getter to protect the original object.

Using the Immutable User Class
Here’s how you can use the User class:

import java.util.Date;

public class Main {
    public static void main(String[] args) {
        // Create a new User object
        Date dob = new Date();
        User user = new User("sachin_test", "sachin@gmail.com", dob);

        // Print the user details
        System.out.println("Original User: " + user);

        // Attempt to modify the date of birth
        dob.setTime(0); // Changing the original Date object
        System.out.println("After Modifying Original Date: " + user);

        // Try modifying the returned date from getter
        Date retrievedDate = user.getDateOfBirth();
        retrievedDate.setTime(0); // Changing the retrieved Date object
        System.out.println("After Modifying Retrieved Date: " + user);
    }
}
Enter fullscreen mode Exit fullscreen mode

Output

Original User: User{username='sachin_test',email='sachin@gmail.com', dateOfBirth=Tue Dec 24 00:00:00 IST 2024}
After Modifying Original Date: User{username='sachin_test', email='sachin@gmail.com', dateOfBirth=Tue Dec 24 00:00:00 IST 2024}
After Modifying Retrieved Date: User{username='sachin_test', email='sachin@gmail.com', dateOfBirth=Tue Dec 24 00:00:00 IST 2024}

As you can see, the User object remains unchanged, proving its immutability.

Why Defensive Copying Matters
Without defensive copying, the immutability of the class can be compromised. For example:

public User(String username, String email, Date dateOfBirth) {
    this.username = username;
    this.email = email;
    // If we don't copy the date, external changes to the original Date object will affect our class
    this.dateOfBirth = dateOfBirth;
}
Enter fullscreen mode Exit fullscreen mode

This would allow changes to the Date object outside the class to affect the User object’s state. Defensive copying prevents this.

Benefits of Immutable User Class
1.Data Safety: The user profile data cannot be altered unintentionally.
2.Thread Safety: The User object can be safely shared between multiple threads.
3.Simplified Debugging: No need to trace how the state of the object changes over time.

Real-Life Applications of Immutable Classes
1.APIs: Immutable classes are widely used in APIs where data integrity is crucial. For example, HttpRequest and HttpResponse objects in web frameworks often use immutability.
2.Multithreading: Immutable objects eliminate synchronization issues in multithreaded environments.
3.Data Modeling: Immutable classes are ideal for representing fixed data, like user profiles, currency, or coordinates.

Conclusion

Creating immutable classes in Java is a powerful way to design reliable, thread-safe, and maintainable code. By following the principles of immutability, you can ensure the integrity of your objects and prevent unintended side effects.

The User example above demonstrates how to implement an immutable class while addressing potential pitfalls like mutable fields. Now it’s your turn—try creating your own immutable classes and see how they make your code cleaner and safer!

Top comments (0)