Immutable Class
- Introduction
- Properties
- How to make an immutable class
- Miscellaneous
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.
Example :
package com.journaldev.java;
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;
}
/**
* Accessor function for mutable objects
*/
public HashMap<String, String> getTestMap() {
//return testMap;
return (HashMap<String, String>) testMap.clone();
}
/**
* Constructor performing Deep
* @param i
* @param n
* @param hm
*/
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;
}
/**
* Constructor performing Shallow
* @param i
* @param n
* @param hm
*/
/**
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Shallow for Object initialization");
this.id=i;
this.name=n;
this.testMap=hm;
}
*/
/**
* To test the consequences of Shallow and how to avoid it with Deep for creating immutable classes
* @param args
*/
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);
//Lets see whether its copy by field or reference
System.out.println(s==ce.getName());
System.out.println(h1 == ce.getTestMap());
//print the ce values
System.out.println("ce id:"+ce.getId());
System.out.println("ce name:"+ce.getName());
System.out.println("ce testMap:"+ce.getTestMap());
//change the local variable values
i=20;
s="modified";
h1.put("3", "third");
//print the values again
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());
}
}
Output :
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.
Example
package com.journaldev.design.builder;
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