7 min to read
Immutable Class in Java
Immutable objects are instances whose state doesn’t change after it has been initialized.
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
- An immutable class is good for caching purpose because you don’t need to worry about the value changes.
- Immutable class is inherently thread-safe, so you don’t need to worry about thread safety in case of multi-threaded environment.
- The references to the immutable objects can be easily shared or cached without having to copy or clone them as there state can not be changed ever after construction.
- The Immutable Class can be used as Keys of a map and values of a Set
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 :-
- Declare the class as final so it can’t be extended.
- Make all fields private so that direct access is not allowed.
- Don’t provide setter methods for variables
- Make all mutable fields final so that it’s value can be assigned only once.
- Initialize all the fields via a constructor performing deep copy.
- 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
- The immutable class should have only getter methods.
- The immutable class will have a private constructor with Builder object as parameter that will be used to create the immutable class.
- 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