Immutable Class in Java

Immutable objects are instances whose state doesn’t change after it has been initialized.

Featured image

Immutable Class in Java

Introduction

Today we will learn how to create an immutable class in Java. Immutable objects are instances whose state doesn’t change after it has been initialized.

String is a good example for Immutable Class in Java. All wrapper classes in java.lang are immutable – String, Integer, Boolean, Character, Byte, Short, Long, Float, Double, BigDecimal, BigInteger

Properties

How to make an immutable class

There are no specific rules to create immutable objects, the idea is to restrict the access of the fields of a class after initialization.

To create a Immutable Class, we can follow the following steps :-

  1. Declare the class as final so it can’t be extended.
  2. Make all fields private so that direct access is not allowed.
  3. Don’t provide setter methods for variables
  4. Make all mutable fields final so that it’s value can be assigned only once.
  5. Initialize all the fields via a constructor performing deep copy.
  6. Perform cloning of objects in the getter methods to return a copy rather than returning the actual object reference.
import java.util.HashMap;
import java.util.Iterator;

public final class FinalClassExample {

	private final int id;
	private final String name;
	private final HashMap<String,String> testMap;

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public HashMap<String, String> getTestMap() {
		//return testMap;
		return (HashMap<String, String>) testMap.clone();
	}

	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Deep  for Object initialization");
		this.id=i;
		this.name=n;
		HashMap<String,String> tempMap=new HashMap<String,String>();
		String key;
		Iterator<String> it = hm.keySet().iterator();
		while(it.hasNext()){
			key=it.next();
			tempMap.put(key, hm.get(key));
		}
		this.testMap=tempMap;
	}

	public static void main(String[] args) {
		HashMap<String, String> h1 = new HashMap<String,String>();
		h1.put("1", "first");
		h1.put("2", "second");		
		String s = "original";		
		int i=10;		
		FinalClassExample ce = new FinalClassExample(i,s,h1);
		System.out.println(s==ce.getName());
		System.out.println(h1 == ce.getTestMap());
		System.out.println("ce id:"+ce.getId());
		System.out.println("ce name:"+ce.getName());
		System.out.println("ce testMap:"+ce.getTestMap());
		i=20;
		s="modified";
		h1.put("3", "third");
		System.out.println("ce id after local variable change:"+ce.getId());
		System.out.println("ce name after local variable change:"+ce.getName());
		System.out.println("ce testMap after local variable change:"+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "new");
		System.out.println("ce testMap after changing variable from accessor methods:"+ce.getTestMap());

	}

}
Performing Deep  for Object initialization
true
false
ce id:10
ce name:original
ce testMap:{2=second, 1=first}
ce id after local variable change:10
ce name after local variable change:original
ce testMap after local variable change:{2=second, 1=first}
ce testMap after changing variable from accessor methods:{2=second, 1=first}

Miscellaneous

Immutable classes can also be created using builder pattern. Builder Pattern is a better option if the immutable class has a lot of attributes and some of them are optional.
Using builder pattern to create immutable class is a good approach when the number of arguments in the Constructor is more that can cause confusion in their ordering.

Steps to follow

  1. The immutable class should have only getter methods.
  2. The immutable class will have a private constructor with Builder object as parameter that will be used to create the immutable class.
  3. If the immutable class attributes are not immutable, for example HashMap, we should perform deep copy or cloning to avoid modification of its attributes.
import java.util.HashMap;

public class ImmutableClass {
	
	//required fields
	private int id;
	private String name;
	
	//optional fields
	private HashMap<String, String> properties;
	private String company;
	
	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public HashMap<String, String> getProperties() {
		//return cloned object to avoid changing it by the client application
		return (HashMap<String, String>) properties.clone();
	}

	public String getCompany() {
		return company;
	}

	private ImmutableClass(ImmutableClassBuilder builder) {
		this.id = builder.id;
		this.name = builder.name;
		this.properties = builder.properties;
		this.company = builder.company;
	}
	
        //Builder class
	public static class ImmutableClassBuilder{
		//required fields
		private int id;
		private String name;
		
		//optional fields
		private HashMap<String, String> properties;
		private String company;
		
		public ImmutableClassBuilder(int i, String nm){
			this.id=i;
			this.name=nm;
		}
		
		public ImmutableClassBuilder setProperties(HashMap<String,String> hm){
			this.properties = (HashMap<String, String>) hm.clone();
			return this;
		}
		
		public ImmutableClassBuilder setCompany(String comp){
			this.company = comp;
			return this;
		}
		
		public ImmutableClass build(){
			return new ImmutableClass(this);
		}
	}
}

Reference :: Oracle Documentation