How to create Immutable class

Today we will learn how to create immutable class in java. Immutable objects are instances whose state doesn’t change after it has been initialized. For example, String is an immutable class and once instantiated its value never changes.

Immutable Class in Java

Immutable class is good for caching purpose because you don’t need to worry about the value changes. Other benefit of immutable class is that it is inherently thread-safe, so you don’t need to worry about thread safety in case of multi-threaded environment.

Read: Java Thread Tutorial and Java Multi-Threading Interview Questions.

Here I am providing a way to create immutable class in java via an example for better understanding.

To create immutable class in java, you have to do 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.

There are many immutable classes like String, Boolean, Byte, Short, Integer, Long, Float, Double etc. In short, all the wrapper classes and String class is immutable. We can also create immutable class by creating final class that have final data members as the example given below

Example to create Immutable class

In this example, we have created a final class named Employee. It have one final datamember, a parameterized constructor and getter method.

import java.io.*; public final class Test { final String pancardNumber; public Test(String pancardNumber){ this.pancardNumber=pancardNumber; } public String getPancardNumber(){ return pancardNumber; } }


Output:
Exception in thread "main" java.lang.NoSuchMethodError: main

The above class is immutable because

  1. The instance variable of the class is final i.e. we cannot change the value of it after creating an object.

  2. The class is final so we cannot create the subclass.

  3. There is no setter methods i.e. we have no option to change the value of the instance variable.

These points makes this class as immutable.

Another sample Final class that works well and values doesn’t get altered after instantiation.

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 testMap; public int getId() { return id; } public String getName() { return name; } /** * Accessor function for mutable objects */ public HashMap getTestMap() { //return testMap; return (HashMap) testMap.clone(); } /** * Constructor performing Deep * @param i * @param n * @param hm */ public FinalClassExample(int i, String n, HashMap hm){ System.out.println("Performing Deep for Object initialization"); this.id=i; this.name=n; HashMap tempMap=new HashMap(); String key; Iterator 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 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 h1 = new HashMap(); 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 hmTest = ce.getTestMap(); hmTest.put("4", "new"); System.out.println("ce testMap after changing variable from accessor methods:"+ce.getTestMap()); } }


Output:
Performing Deep Copy 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}

Now let’s comment the constructor providing deep copy and uncomment the constructor providing shallow copy. Also uncomment the return statement in getTestMap() method that returns the actual object reference and then execute the program once again.

Performing Shallow Copy for Object initialization true true 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:{3=third, 2=second, 1=first} ce testMap after changing variable from accessor methods:{3=third, 2=second, 1=first, 4=new}

As you can see from the output, HashMap values got changed because of shallow copy in the constructor and providing direct reference to the original object in the getter function.




Instagram