How to create Immutable Objects with Example

Immutable objects are those objects that cannot be modified once created. Please read the first article in Immutable Objects series to understand what advantages are provided by immutable objects.

We need to follow few simple rules when we write class whose objects are to be immutable. Below are few rules to follow to create immutable objects:

1. Make class final or create private constructor and provide static factory

By making class final or creating private constructor we ensure that some malicious subclass which can compromise the immutability by changing internals of class won’t extend the class. How to do this?

2. Make class final or provide static factory

public final class Name {
 
    public Name(String firstName, String lastName) {
 
    }
}

3. Make constructor private and provide static factory to create instance of class.

public class Name {
 
    private Name(String firstName, String lastName) {
 
    }
     
    public static Name of(String firstName, String lastName) {
        return new Name(firstName, lastName);
    }
 
}

4. Make all attributes of class private and final and initialize them in constructor

Making attributes private is good habit, so these attributes cannot be modified outside the class. Making mutable attributes private prevents another class from modifying them. I always mark attributes private unless needed otherwise. Till date I have marked attributes in class as private, never found a reason not to do so.

Making attributes final will force us to initialize them in constructor. This also means that we cannot assign new values to that attribute ever again once an object is created. Hence, our intent is clear that we will not let attributes inside a class get modified once we create object. See how our Name class is forming into a mature class rather than POJO which was amateur.

For sake of simplicity, I am not adding any checks for parameters in static factory. Read more on that here. Do not initialize an object in an inconsistent state.

public class Name {
 
    private final String firstName;
    private final String lastName;
     
    private Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
     
    public static Name of(String firstName, String lastName) {
        // Place checks of parameters. 
        // Throw IllegalArgumentException if checks fail.
        return new Name(firstName, lastName);
    }
 
}

5. Make defensive copies of mutable attributes in constructors and accessors

This is an important rule. If we accept an object that is mutable, example java.util.Date then we need to create new java.util.Date object and copy the internals of it into a new object. Why exactly? Because we just use the incoming object that is mutable and if that object is changed, then our immutability is broken. Also, while exposing such mutable objects, we need to create and return new object.

// java.util.Date class in Java is mutable.
Date date = new Date(); // Mon Feb 11 17:40:28 PST 2019
date.setTime(System.currentTimeMillis() - 1000); // Mon Feb 11 17:40:27 PST 2019
private static App of(Date date) {
 
    //copy internals of mutable object.
    Date newDate = new Date(date.getTime()); 
    // more code;
 
}

6. Do not provide any setter/mutator methods

Once the object is created, we must not modify it to maintain its immutability. Hence, we must not provide any setter/mutator methods.

7. Not a rule, but better to do :

Override equals, hashCode and toString method.

Implement Comparable<> interface.

Below is Name class once designed with above rules:

public class Name implements Comparable<Name> {
 
	private String firstName;
	private String lastName;
 
	private Name(
		String firstName, 
		String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}
     
	/**
	* Static factory that creates new Name with givenName and 
	* lastName.
	* @param firstName
	* @param lastName
	* 
	* @throws IllegalArgumentException if givenName or lastName 
	* is null or empty
	*/
	public static Name of(
		String firstName, 
		String lastName) {
     
		boolean isNotValidGivenName = givenName == null 
										|| givenName.length() == 0;

		boolean isNotValidLastName = lastName == null 
										|| lastName.length() == 0;
     
		if (isNotValidGivenName || isNotValidLastName) {
			throw new IllegalArgumentException(
					"Value must not be null or empty.");
		}
 
		return new Name(firstName, lastName);
	}
 
	public String firstName() {
		return firstName;
	}
 
	public String lastName() {
		return lastName;
	}
     
	public int compareTo(Name name) {
		int result = this.lastName.compareTo(name.lastName);
		if (result == 0) {
			result = this.firstName.compareTo(name.firstName);
		}
		return result;
    }
 
    // auto generate equals hashCode and toString methods

}

8.Conclusion

That is all about creating Immutable Objects. Using Immutable Objects is really easy as you don’t have deal with different states of the same object. Once again, use immutable objects as often as you can. Dealing with the state can be easy in a single threaded environment but it can become a nightmare in multi-threaded environment.

Leave a Reply

Your email address will not be published. Required fields are marked *