Immutable object in Java- How to create immutable object in Java?

By | December 17, 2023

In Java, immutable objects are objects whose state cannot be changed once they are created; they will remain constant throughout the lifecycle of the object. It helps in terms of simplicity, thread-safety and security. For example, string objects, In Java, string objects are immutable. i.e., once they are initialized, they cannot be changed. To learn more about the immutability of a string class, please refer Why use the char[] array for storing passwords over strings in Java?. In this tutorial, we will learn how to create immutable object in Java.

Rules to create immutable object in Java

In Java, we can create an immutable object with the help of following rules:

  1. Make the class “final”: This will prevent the class from being extended and modified from the other classes.
  2. Make all the fields “private”: Making fields private will not allow the direct access to the fields.
  3. Do not provide setter methods: Avoiding setters will ensure not to modify the state of object.
  4. Make fields “final” that are mutable: It ensures that once the value is assigned to the fields, it can not be reassigned.
  5. Deep copy in Constructor: This ensures that the internal state of the object remains truly immutable by preventing external modification through references to mutable objects.
  6. Deep copy in getter: If we have mutable objects as fields in a class and we want to ensure that clients of our class cannot modify the internal state, we might consider returning a copy of the field rather than a direct reference.

1. Mutable object in Java

First, we will understand what a mutable object is. For this, we will be using an employee example. We first create its object, and then we change its attributes to understand its mutable behaviour.

1.1- Create Employee class

First of all, we need a class for the object. For this, we are going to create an Employee class.

Java
import java.util.List;
import java.util.Objects;

/**
 * 1. Employee<id, name, role, technologies> 
 * 2. toString()- used to print the Object's
 * 3. value hashCode()- JVM will assign a unique value to every object, this method will return that unique value
 * 
 * @author paulsofts
 */

public class Employee {

	int id;
	String name;
	String role;
	List<String> technologies;

	public Employee(int id, String name, String role, List<String> technologies) {
		super();
		this.id = id;
		this.name = name;
		this.role = role;
		this.technologies = technologies;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", role=" + role + ", technologies=" + technologies + "]";
	}

	@Override
	public int hashCode() {
		return Objects.hash(id, name, role, technologies);
	}

}

1.2- Main class

Now, we will need a Main class that contains the main (String[] args) method to start the program execution.

Java
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * 1. List<String> list: contains the technologies employee knows
 * 2. We are using all args constructor to create employee object
 * 3. init(): used to print the hashcode and the employee details
 * 
 * @author paulsofts
 */

public class Main {

	Logger logger = Logger.getLogger(getClass().getName());

	public static void main(String[] args) {
		Main main = new Main();

		List<String> list = new ArrayList<>();
		list.add("Java");
		list.add("Spring Boot");
		list.add("Jenkins");
		list.add("Kafka");
		list.add("MySQL");
		list.add("Mongo");

		Employee employee = new Employee(101, "Ankur", "Developer", list);
		main.init(employee);
	}

	public void init(Employee employee) {
		logger.info("Object hashcode: " + employee.hashCode());
		logger.info("Object details: " + employee);
	}

}

Below is the output for the following: As we can see, we are getting the hashcode and the object details in the logs.

Fig 1- Employee Object

1.3- Update the employee name

In the Main class, we will update the name of the employee using employee.name and change it to some other name.

Java
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * 1. List<String> list: contains the technologies employee knows
 * 2. We are using all args constructor to create employee object
 * 3. Update the name of employee
 * 4. init(): used to print the hashcode and the employee details
 * 
 * @author paulsofts
 */

public class Main {

	Logger logger = Logger.getLogger(getClass().getName());

	public static void main(String[] args) {
		Main main = new Main();

		List<String> list = new ArrayList<>();
		list.add("Java");
		list.add("Spring Boot");
		list.add("Jenkins");
		list.add("Kafka");
		list.add("MySQL");
		list.add("Mongo");

		Employee employee = new Employee(101, "Ankur", "Developer", list);
		//Update the name of the employee
		employee.name = "Sahil";
		main.init(employee);
	}

	public void init(Employee employee) {
		logger.info("Object hashcode: " + employee.hashCode());
		logger.info("Object details: " + employee);
	}

}

After we have updated the name of employee, we are getting following output:

Fig 2- Employee object

As we see, the hashcode value gets changed, which means the employee object gets changed, and so the employee object is not immutable.

2. Create immutable object in Java

We use the above-mentioned rules and update our employee class so that its objects become immutable in nature.

2.1 Make Employee class final

We make our Employee class final; the final keyword will ensure that our Employee class cannot be extended or modified by some other classes.

2.2 Make all fields as private

We make all the fields (member variables) of our Employee class as private. It prevent the direct access to the member variables of the class.

2.3 Do not provide setter methods

After we have made all the member variables of the Employee class private, to access them, we need getter methods. But, make sure not to provide the setter methods, as they can be used to modify the state of the employee object.

Until now, our employee class will have the following structure:

Java
import java.util.List;
import java.util.Objects;

/**
 * 1. Employee<id, name, role, technologies> 
 * 2. toString()- used to print the Object's 
 * 3. value hashCode()- JVM will assign a unique value to every object, this method will return that unique value
 * 
 * How to create immutable object? 
 * a) First, we are making Employee class as final 
 * b) Making all fields as private 
 * c) Add getter and remove setter methods
 * 
 * @author paulsofts
 */

public final class Employee {

	private int id;
	private String name;
	private String role;
	private List<String> technologies;

	public Employee(int id, String name, String role, List<String> technologies) {
		super();
		this.id = id;
		this.name = name;
		this.role = role;
		this.technologies = technologies;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public String getRole() {
		return role;
	}

	public List<String> getTechnologies() {
		return technologies;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", role=" + role + ", technologies=" + technologies + "]";
	}

	@Override
	public int hashCode() {
		return Objects.hash(id, name, role, technologies);
	}

}

2.4 Make fields final

In this step, we need to make the fields final, which can be mutable. For example, in our Employee class, we have a field (id, which is of int type) that can be changed. So, we need to make this field final. But, on the safe side, we are making every field as final.

2.5 Deep copy constructor

This is important, we have to make sure we use deep copy constructor in our class. Let’s understand this in detail, we have a field (List<String> technologies) in our Employee class constructor.

Java
public Employee(int id, String name, String role, List<String> technologies) {
		super();
		this.id = id;
		this.name = name;
		this.role = role;
		this.technologies = technologies;
	}

It is a reference variable that contains references to other string values, which means if they change, the reference of the list will also change, making it mutable. In other words, if the list values get changed, it will change the address of the complete list variable, and the changed address will be assigned to the employee class list variable. In order to overcome this issue, we will use deep copy constructor.

Java
public Employee(int id, String name, String role, List<String> technologies) {
		super();
		this.id = id;
		this.name = name;
		this.role = role;
		List<String> temp = new ArrayList<>();
		for(String str : technologies) {
			temp.add(str);
		}
		this.technologies = temp;
	}

2.6 Deep copy getter

Similar to the deep copy inside the constructor, we also need the deep copy inside the getter method for our list variable.

Java
public List<String> getTechnologies() {
		List<String> temp = new ArrayList<>();
		for(String str : technologies) {
			temp.add(str);
		}
		return temp;
	}
Note: Make sure to take care of all the reference variables inside a class while making it mutable.

Now, we have successfully made our employee class immutable. As soon as we create an object of this class, we have an immutable object.

Employee.class

Java
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 1. Employee<id, name, role, technologies> 
 * 2. toString()- used to print the Object's 
 * 3. value hashCode()- JVM will assign a unique value to every object, this method will return that unique value
 * 
 * How to create immutable object? 
 * a) First, we are making Employee class as final 
 * b) Making all fields as private 
 * c) Add getter and remove setter methods
 * d) Making all the field final
 * e) Deep copy inside constructor
 * f) Deep copy inside getter
 * 
 * @author paulsofts
 */

public final class Employee {

	private final int id;
	private final String name;
	private final String role;
	private final List<String> technologies;

	public Employee(int id, String name, String role, List<String> technologies) {
		super();
		this.id = id;
		this.name = name;
		this.role = role;
		List<String> temp = new ArrayList<>();
		for(String str : technologies) {
			temp.add(str);
		}
		this.technologies = temp;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public String getRole() {
		return role;
	}

	public List<String> getTechnologies() {
		List<String> temp = new ArrayList<>();
		for(String str : technologies) {
			temp.add(str);
		}
		return temp;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", role=" + role + ", technologies=" + technologies + "]";
	}

	@Override
	public int hashCode() {
		return Objects.hash(id, name, role, technologies);
	}

}

Main.class

Java
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * 1. List<String> list: contains the technologies employee knows
 * 2. We are using all args constructor to create employee object
 * 3. Update the name of employee
 * 4. init(): used to print the hashcode and the employee details
 * 
 * @author paulsofts
 */

public class Main {

	Logger logger = Logger.getLogger(getClass().getName());

	public static void main(String[] args) {
		Main main = new Main();

		List<String> list = new ArrayList<>();
		list.add("Java");
		list.add("Spring Boot");
		list.add("Jenkins");
		list.add("Kafka");
		list.add("MySQL");
		list.add("Mongo");

		Employee employee = new Employee(101, "Ankur", "Developer", list);
		main.init(employee);
	}

	public void init(Employee employee) {
		logger.info("Object hashcode: " + employee.hashCode());
		logger.info("Object details: " + employee);
		logger.info("Technologies list: " + employee.getTechnologies());
	}

}

As we run our Main.class we will get the following output:

create immutable object in Java
Fig 3- Immutable class output

2.7 Testing

In this step, we will test our immutable Employee class object. We do not have a setter method in our employee class. So, we cannot directly set data for it. Instead, we will get data, change it, and then test for the immutability of employee objects.

Java
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * 1. List<String> list: contains the technologies employee knows
 * 2. We are using all args constructor to create employee object
 * 3. Update the name of employee
 * 4. init(): used to print the hashcode and the employee details
 * 
 * @author paulsofts
 */

public class Main {

	Logger logger = Logger.getLogger(getClass().getName());

	public static void main(String[] args) {
		Main main = new Main();

		List<String> list = new ArrayList<>();
		list.add("Java");
		list.add("Spring Boot");
		list.add("Jenkins");
		list.add("Kafka");
		list.add("MySQL");
		list.add("Mongo");

		Employee employee = new Employee(101, "Ankur", "Developer", list);
		//fetching the list of technologies
		List<String> tech_list = employee.getTechnologies();
		//clear the list-  it will empty the list
		tech_list.clear();
		System.out.println("tech_list contains: " + tech_list);
		main.init(employee);
	}

	public void init(Employee employee) {
		logger.info("Object hashcode: " + employee.hashCode());
		logger.info("Object details: " + employee);
		logger.info("Technologies list: " + employee.getTechnologies());
	}

}

In the above code snippet, we have created tech_list using getter method of employee class and after ward we have cleared the list. We will get the following output for above program:

Immutable object in Java
Fig 4- Output

If we do not do the deep copy inside the constructor, the employee object will get changed. This is because, as we get the list of technologies using getter, it will bring the reference to that list and assign it to the tech_list, and as we clear the tech_list, it will clear values present at that reference and change the object. To understand this, below we have updated the employee class getter for the getTechnologies() method.

Java
package com.paulsofts.Collections.ImmutableObject;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 1. Employee<id, name, role, technologies> 
 * 2. toString()- used to print the Object's 
 * 3. value hashCode()- JVM will assign a unique value to every object, this method will return that unique value
 * 
 * How to create immutable object? 
 * a) First, we are making Employee class as final 
 * b) Making all fields as private 
 * c) Add getter and remove setter methods
 * d) Making all the field final
 * e) Deep copy inside constructor
 * f) Deep copy inside getter
 * 
 * @author paulsofts
 */

public final class Employee {

	private final int id;
	private final String name;
	private final String role;
	private final List<String> technologies;

	public Employee(int id, String name, String role, List<String> technologies) {
		super();
		this.id = id;
		this.name = name;
		this.role = role;
		List<String> temp = new ArrayList<>();
		for(String str : technologies) {
			temp.add(str);
		}
		this.technologies = temp;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public String getRole() {
		return role;
	}

	public List<String> getTechnologies() {
//		List<String> temp = new ArrayList<>();
//		for(String str : technologies) {
//			temp.add(str);
//		}
//		return temp;
		return technologies;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", role=" + role + ", technologies=" + technologies + "]";
	}

	@Override
	public int hashCode() {
		return Objects.hash(id, name, role, technologies);
	}

}

Now, if we run our main class, the object will get changed.

Immutable object in Java
Fig 5- Immutable object

Above, we can see the hascode for the object has changed along with the list of technologies becoming empty.

Leave a Reply

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