Java creating immutable class

A class is immutable if we cannot change the value of its objects once they are created. In Java, String is an immutable class. Its value cannot be changed once it is created. All wrapper classes, like Integer, Float, Long, Double, Boolean, Byte etc., are immutable.

Advantages of Immutable objects:

  • Immutable objects, once created, are not going to change. Therefore, reference to immutable objects can be cached.
  • They are useful in the multi-threaded program as one thread cannot change the value of the other. Thus are thread-safe.

Disadvantages of immutable objects:

  • For every distinct value, they require a new object
  • For any modified version of an immutable object, we need to create a new class

We can create our custom immutable class. The properties which make a class immutable are:

  1. The class must be declared final so that it cannot be extended to another class.
  2. The data members should be declared as private so that they cannot be accessed outside the class
  3. The data members should be declared final so that their values cannot be changed once assigned,
  4. Use the get methods to access the values of the data members since direct access is not allowed.
  5. A parameterized constructor should initialize all the data members. Perform a deep copy so they cannot be modified with an object reference.
  6. Do not provide any set method to change the values of any data member. Their values are assigned once only through the constructor.
  7. While returning the value of data, members perform a deep copy. Do not return an object reference

Code Example: Create an Immutable class

			

//  Creating an immutable class in Java

import java.util.HashMap;
import java.util.Map;

//  Nominee is an immutable class. 
//  Object of this class contains nominee information of Provident Fund. 
//  Each employee may have any number of nominees
//  For each nominee – name, relation and percentage are maintained

final class Nominee {  // class is final
    //  All variables are private final
    private final int empno;       // Employee No
    private final String empname;  // Employee Name
    private final int nominee_no;  // Number of nominees

    private final Map<String, String[]>nomineeMap; // Nominee details

    // Parameterized Constructor 
    public Nominee(int empno, String empname, Map<String, String[]>nomineeMap)
    {
	this.empno = empno;
	this.empname = empname;
        //  Create a temp Map object to copy nominee details passed as a parameter
        //  Deep copying the values
        Map<String, String[]>tempMap = new HashMap<>();
        for (Map.Entry<String, String[]> entry :
		nomineeMap.entrySet()) {
		tempMap.put(entry.getKey(), entry.getValue());
        }
	this.nomineeMap = tempMap;  // Assign the nominee details to nomineeMap
	this.nominee_no = tempMap.size();  // Number of nominees
    }

    public int getEmpNo() { return empno; } // get method for emp no

    public String getEmpName() { return empname; } // get method for emp name

    public int getNomineeNo() { return nominee_no; } // get method for nominee count

    // Nominee Details are captured in a string for print/display
    public String getNomineeDetail()   // get method for nominee details
    {
        String s = "";
        for (Map.Entry<String, String[]> entry :
		this.nomineeMap.entrySet()) {
		String serial = entry.getKey();
		String nominee[] = entry.getValue();
		s = s + serial+". Name: "+nominee[0]+" Relation: "+nominee[1]+" Percentage: "+nominee[2]+"\n";
        }
        return s;
    }
}

class TestM {

    // Main driver method
    public static void main(String[] args)
    {
        // Creating Map object with reference to HashMap
        Map<String, String[]>nomineemap = new HashMap<>();

        // Adding elements to Map object 
		nomineemap.put("1", new String[]{"Pinaki Bhadra","Son","25%"});
		nomineemap.put("2", new String[]{"Soma Bhadra","Wife","50%"});
		nomineemap.put("3", new String[]{"Tamia Bhadra","Daughter","25%"});

		Nominee nom = new Nominee(1251, "Amlan Bhadra", nomineemap);

		System.out.println("Employee Name: "+nom.getEmpName());
		System.out.println("Emplyeee No.: "+nom.getEmpNo());
		System.out.println("Number of Nominees: "+nom.getNomineeNo());
		System.out.println(nom.getNomineeDetail());

        // Remains unchanged due to deep copy in getter
		nomineemap.put("4", new String[]{"Shouvik Bhadra","Son","25%"});
		System.out.println(nom.getNomineeDetail());
    }
}

				

Output:

Employee Name: Amlan Bhadra Emplyeee No.: 1251 Number of Nominees: 3 1. Name: Pinaki Bhadra Relation: Son Percentage: 25% 2. Name: Soma Bhadra Relation: Wife Percentage: 50% 3. Name: Tamia Bhadra Relation: Daughter Percentage: 25% 1. Name: Pinaki Bhadra Relation: Son Percentage: 25% 2. Name: Soma Bhadra Relation: Wife Percentage: 50% 3. Name: Tamia Bhadra Relation: Daughter Percentage: 25%

In this example, we have created a final class named Nominee. It has four final data members, a parameterized constructor, and four get methods. Please note that there is no set method here. The addition of another nominee was not accepted, as is evident from the output.