How To Create Custom Immutable Class In Java

Immutable Class in Java

In this tutorial I am going to show you how to create custom immutable class in Java programming language. Immutable objects are those objects whose states cannot be changed. For example, you can change the state of an object by changing its attribute’s or field’s value. String is an example of an immutable class in Java. There are other examples of immutable classes in Java, such as, all primitive wrapper classes are immutable classes – Integer, Byte, Long, Float, Double, Character, Boolean, Short. In Java 8, the Date Time API made all classes to be immutable, for example, LocalDate, LocalTime, LocalDateTime, etc.

When you have the requirement to make your class instance immutable then you should have a strategic plan or sophisticated analysis. You may have a good reason why instances of the class never change after construction. There are some simple rules for creating immutable class in Java and it may not happen that all classes classified as immutable will follow these rules.

Steps to create Immutable Objects

The following rules define a simple strategy for creating immutable objects in Java:

  1. You shouldn’t allow your class to be sub-classed for overriding methods. The simplest way to do this is make the class final. A more elegant way is to create a private constructor and construct the objects or instances in factory methods.
  2. Make all fields or attributes in a class as private and final.
  3. Do not provide any setter methods for fields in the class:
    • Initialize all fields through constructor or in the factory method
  4. If the instance fields include references to the mutable objects, don’t allow those objects to be changed:
    • Don’t provide methods that modify mutable objects
    • Don’t share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods

Advantages of Immutable Objects

There are two main reasons you may want to make your objects immutable:

  1. Immutable objects are simpler as once they are created there is no way to change their state. So there is no side effect on it as any possible modification method will create and return a new instance.
  2. Immutable objects are inherently thread-safe. They can be easily used in a multi-threaded environment and they cannot be corrupted by multiple threads accessing them concurrently.
  3. Immutable objects do not change their value, you can easily cache them because their value won’t change.
  4. Immutable objects are good candidate for hash keys because their hash code can be cached and reused for better performance. That’s why it is recommended to use immutable object as a key in HashMap.

Example of Object’s State Change

Here I am going to show a simple example how object’s state gets changed. Consider the following class:

public class ColorRGB {

    private int red;
    private int green;
    private int blue;
    private String name;

    public ColorRGB(int red, int green, int blue, String name) {
	this.red = red;
	this.green = green;
	this.blue = blue;
	this.name = name;
    }
	
    public void set(int red, int green, int blue, String name) {
	this.red = red;
	this.green = green;
	this.blue = blue;
	this.name = name;
    }

    public int getRGB() {
        return ((red << 16) | (green << 8) | blue);
    }

    public String getName() {
        return name;
    }

}

In the above class I have defined a method set() that sets the value for red, green, blue and name of the color.

Let’s suppose, a thread executes the following piece of code:

ColorRGB color = new ColorRGB(0, 0, 0, "Black");

int myColorValue = color.getRGB(); //Statement 1
String myColorName = color.getName(); //Statement 2

If another thread invokes color.set() after Statement 1 but before Statement 2, the value of myColorValue variable won’t match the value of myColorName variable. So here is what you see how your object’s state gets changed. You could have a setter method for individual field instead of set() method to set all fields at once.

Or you might have the following code snippets for changing the object’s state:

ColorRGB color = new ColorRGB(0, 0, 0, "Black");

System.out.println("Black color ref: " + color);

color.set(220, 20, 60, "Crimson"); // Change state of object

System.out.println("Crimson color ref: " + color);

int myColorValue = color.getRGB(); // Statement 1
String myColorName = color.getName(); // Statement 2

System.out.println("myColorValue: " + myColorValue + ", myColorName: " + myColorName);

The above code snippets will give you the following output:

Black color ref: com.roytuts.java.custom.immutable.clasz.ColorRGB@4517d9a3
Crimson color ref: com.roytuts.java.custom.immutable.clasz.ColorRGB@4517d9a3
myColorValue: 14423100, myColorName: Crimson

If you see the object reference value then you will notice they are pointing to the same object. So you have changed the state of the same object.

Immutable Class/Object

Now I am going to make the above class as an immutable class.

public final class ImmutableColorRGB {

	private final int red;
	private final int green;
	private final int blue;
	private final String name;

	public ImmutableColorRGB(int red, int green, int blue, String name) {
		this.red = red;
		this.green = green;
		this.blue = blue;
		this.name = name;
	}

	public int getRGB() {
		return ((red << 16) | (green << 8) | blue);
	}

	public String getName() {
		return name;
	}

}

In the above class, you see that I have made all instance variables as private and final. I removed the set() method to prevent any (intentional/unintentional) modification to the state of the object.

Now create instance to test the above program by executing the following code snippets:

ImmutableColorRGB color = new ImmutableColorRGB(0, 0, 0, "Black");

System.out.println("Black color ref: " + color);

color = new ImmutableColorRGB(220, 20, 60, "Crimson");

System.out.println("Crimson color ref: " + color);

int myColorValue = color.getRGB(); // Statement 1
String myColorName = color.getName(); // Statement 2

System.out.println("myColorValue: " + myColorValue + ", myColorName: " + myColorName);

Executing the above code snippets you will get the following output:

Black color ref: com.roytuts.java.custom.immutable.clasz.ImmutableColorRGB@4517d9a3
Crimson color ref: com.roytuts.java.custom.immutable.clasz.ImmutableColorRGB@372f7a8d
myColorValue: 14423100, myColorName: Crimson

From the above output it is clear that any modification to the state of object creates a new instance because object reference values for two colors are different.

Private Constructor and Factory method in Immutable Class

Now I will show you a more sophisticated way to create immutable objects using private constructor and factory method in the class.

I will modify the above class to introduce private constructor instead of public constructor and add a factory method to initialize the values for RGB.

public final class ImmutableFactoryMethodColorRGB {

	private int red;
	private int green;
	private int blue;
	private String name;

	private ImmutableFactoryMethodColorRGB(int red, int green, int blue, String name) {
		this.red = red;
		this.green = green;
		this.blue = blue;
		this.name = name;
	}

	public static ImmutableFactoryMethodColorRGB instance(int red, int green, int blue, String name) {
		return new ImmutableFactoryMethodColorRGB(red, green, blue, name);
	}

	public int getRGB() {
		return ((red << 16) | (green << 8) | blue);
	}

	public String getName() {
		return name;
	}

}

Now create instance to test the above program by executing the following code snippets:

ImmutableFactoryMethodColorRGB color = ImmutableFactoryMethodColorRGB.instance(0, 0, 0, "Black");

System.out.println("Black color ref: " + color);

color = ImmutableFactoryMethodColorRGB.instance(220, 20, 60, "Crimson");

System.out.println("Crimson color ref: " + color);

int myColorValue = color.getRGB(); // Statement 1
String myColorName = color.getName(); // Statement 2

System.out.println("myColorValue: " + myColorValue + ", myColorName: " + myColorName);

Running the above code snippets will give you the same output as you have got from the above immutable class ImmutableColorRGB.

Black color ref: com.roytuts.java.custom.immutable.clasz.ImmutableFactoryMethodColorRGB@4517d9a3
Crimson color ref: com.roytuts.java.custom.immutable.clasz.ImmutableFactoryMethodColorRGB@372f7a8d
myColorValue: 14423100, myColorName: Crimson

So you got an idea how to use private constructor and factory method to create instance from immutable class instead of using public constructor to create an instance.

Mutable Object in Immutable Class/Object

Now I will show you how to use mutable instance in immutable class. Let’s say you need to store a person’s address into a separate class. Let’s also assume that you need to have two classes – Person and Address.

The Address class is given as follows:

public class Address implements Cloneable {

	private String city;
	private String country;

	public Address(String city, String country) {
		this.city = city;
		this.country = country;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public String getCountry() {
		return country;
	}

	public void setCountry(String country) {
		this.country = country;
	}

        @Override
	public String toString() {
		return "Address [city=" + city + ", country=" + country + "]";
	}

}

The Person class is given as follows:

public final class Person {

	private final String name;
	private final Address address;

	public Person(String name, Address address) {
		this.name = name;
		this.address = address;
	}

	public String getName() {
		return name;
	}

        public Address getAddress() {
		return address;
	}

}

So the above Person class is immutable and you have the mutable instance Address object in Person class.

In the above Address class, I have created getters and setters and the class Person having this Address object is no more immutable. Immutability in Person class can easily be broken.

Let’s check how to break immutability in the Person class.

public class PersonImmutabilityTest {

	public static void main(String[] args) {
		Person person = new Person("Soumitra", new Address("City", "Country"));

		Address address = person.getAddress();
		System.out.println(address);

		address.setCity("City1");
		address.setCountry("Country1");
		System.out.println("After: " + address);
	}

}

So you will see the following output from the above code:

Address [city=City, country=Country]
After: Address [city=City1, country=Country1]

Here I got the reference of address object using getAddress() method and stored into a new local variable. This reference still points to the same address instance. Then I changed the value of city and country fields of the address instance and it updated the address instance used by Person object. Hence when I tried to get the address of the person, it gave me the updated address. So Person class is no longer an immutable class.

How can you make the Person class immutable?

There are two options – one is to remove setter methods from Address class and you can create child class that will extend Address class. The first option may not be possible in case the class is from a third party library or jar. Then you have only the second option left. Now create the following MyAddress that extends Address class.

public class MyAddress extends Address implements Cloneable {

	public MyAddress(String city, String country) {
		super(city, country);
	}

	@Override
	public void setCity(String city) {
		throw new UnsupportedOperationException();
	}

	@Override
	public void setCountry(String country) {
		throw new UnsupportedOperationException();
	}

	@Override
	public Object clone() {
		try {
			return super.clone();
		} catch (Exception e) {
			return null;
		}
	}
}

Update the Person class as below:

public final class Person {

	private final String name;
	private final MyAddress address;

	public Person(String name, MyAddress address) {
		this.name = name;
		this.address = address;
	}

	public String getName() {
		return name;
	}

	public MyAddress getAddress() {
		return (MyAddress) address.clone();
	}

}

Update your main class also:

public class PersonImmutabilityTest {

	public static void main(String[] args) {
		Person person = new Person("Soumitra", new MyAddress("City", "Country"));

		Address address = person.getAddress();
		System.out.println(address);

		address.setCity("City1");
		address.setCountry("Country1");
		System.out.println("After: " + address);
	}

}

Now executing the above class you will see the following output:

Address [city=City, country=Country]
Exception in thread "main" java.lang.UnsupportedOperationException
	at com.roytuts.java.custom.immutable.clasz.MyAddress.setCity(MyAddress.java:11)
	at com.roytuts.java.custom.immutable.clasz.PersonImmutabilityTest.main(PersonImmutabilityTest.java:12)

So I solved the problem but what happens if there are more setters methods added to the Address class in future then I need to override those setter methods in future also. Therefore the solution will be – you can return the clone object for the Address instance. In this case even if the third party library or jar changes anything to the original Address, it is not going the affect the original Address object in the Person object. For this scenario, the Address class has to implement Cloneable interface and has the clone() method implemented. For most of the cases the third party library does implement it.

The Address class looks similar to the below code:

public class Address implements Cloneable {

	private String city;
	private String country;

	public Address(String city, String country) {
		this.city = city;
		this.country = country;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public String getCountry() {
		return country;
	}

	public void setCountry(String country) {
		this.country = country;
	}

	@Override
	public Object clone() {
		try {
			return super.clone();
		} catch (Exception e) {
			return null;
		}
	}
	
	@Override
	public String toString() {
		return "Address [city=" + city + ", country=" + country + "]";
	}

}

The Person class has the following code:

public final class Person {

	private final String name;
	private final Address address;

	public Person(String name, Address address) {
		this.name = name;
		this.address = address;
	}

	public String getName() {
		return name;
	}

	public Address getAddress() {
		return (Address) address.clone();
	}

}

Test the immutability of the Person class:

public class PersonImmutabilityTest {

	public static void main(String[] args) {
		Person person = new Person("Soumitra", new Address("City", "Country"));

		Address address = person.getAddress();
		System.out.println(address);

		address.setCity("City1");
		address.setCountry("Country1");
		
		System.out.println("person.getAddress(): " + person.getAddress());
		
		System.out.println("After: " + address);
	}

}

Now running the above class you will get the following output:

Address [city=City, country=Country]
person.getAddress(): Address [city=City, country=Country]
After: Address [city=City1, country=Country1]

From the above output you see that address in the Person object has not been changed.

Now what if your Address class does not implement Cloneable interface and you do not have idea also whether the Address class implements Cloneable interface or not. Then you can go with the deep copy of the Address object and return it.

Now your Address class looks similar to the below code:

public class Address {

	private String city;
	private String country;

	public Address(String city, String country) {
		this.city = city;
		this.country = country;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public String getCountry() {
		return country;
	}

	public void setCountry(String country) {
		this.country = country;
	}
	
	@Override
	public String toString() {
		return "Address [city=" + city + ", country=" + country + "]";
	}

}

Your Person class looks similar to the below code:

public final class Person {

	private final String name;
	private final Address address;
	
	public Person(String name, Address address) {
		this.name = name;
		this.address = new Address(address.getCity(), address.getCountry());
	}

	public String getName() {
		return name;
	}
	
	public Address getAddress() {
		Address addr = new Address(address.getCity(), address.getCountry());
		return addr;
	}
	
}

Your main class looks same as you did for clone():

public class PersonImmutabilityTest {

	public static void main(String[] args) {
		Person person = new Person("Soumitra", new Address("City", "Country"));

		Address address = person.getAddress();
		System.out.println(address);

		address.setCity("City1");
		address.setCountry("Country1");
		
		System.out.println("person.getAddress(): " + person.getAddress());
		
		System.out.println("After: " + address);
	}

}

Now you will get the same output as shown below:

Address [city=City, country=Country]
person.getAddress(): Address [city=City, country=Country]
After: Address [city=City1, country=Country1]

Hope you have got idea on how to create immutable class in Java and how to handle reference to the mutable instance.

Source Code

Download

Leave a Reply

Your email address will not be published.