This article shows how to create immutable classes in Java using suitable examples for a better explanation.
Immutable class in Java
Immutable classes are those classes whose object's state cannot be changed once created or any modification results in a new immutable object.
To better understand let's use a simple example of String and StringBuffer classes, any modification of a String object, in other words using substring, concat, and so on. results in a new immutable object when an operation is performed on the StringBuffer class but does not create a new immutable object. Instead it reflects the changes on the existing object only. Even JDK itself contains a number of immutable classes, like String, Float and other wrapper classes. We can also create an immutable class by creating a final class that has final data members.
Guidelines for making an immutable class in Java
The following are simple guidelines that can be followed to make a class immutable in Java:
- Do not provide a setter method for the class because setter methods are used to change the state of the class's members.
- Make all the fields final and private so that fields become non-accessible from outside the class and also their values will not be altered because of being final.
- The class should not be sub-classed, so make them final that will prevent it from being overridden.
- There is a need to focus on mutable reference variables with a reference variable, even final cannot prevent their values to be changed. So to prevent the reference variable's values, it's better to provide a copy of the object instead of providing an actual object.
- Make the default constructor of the class private and provide instances with a factory method.
Example of making a class immutable by creating a final class
In the example given below, we create a final class named Student. There is only one final data member, a constructor and a getter method.
public final class Student
{
final int RollNumber;
public Student(int RollNumber)
{
this.RollNumber=RollNumber;
}
public int getRollNumber()
{
return RollNumber;
}
}
The class in the preceding example is an immutable class because:
- The instance variable of the class is final so after creating it we cannot change its value.
- We cannot create a sub-class because the class is declared as final.
- There is no setter method so there is no chance of changing the value of the instance variable.
Another example
Student.java
import java.util.Date;
// making class final which prevents it's methods overriden in subclasses
public final class Student
{
private int RollNumber;
private String Name;
private Date CurrentDate;
// private constructor ensures of no unwanted creation of objects
private Student(int RollNumber, String Name, Date CurrentDate)
{
this.RollNumber=RollNumber;
this.Name=Name;
this.CurrentDate=CurrentDate;
}
// this holds the logic of object creation, only point to get an object
static Student getInstance(int f1,String f2,Date f3)
{
return new Student(f1,f2,f3);
} // integer class is immutable, this can be served directly
public int getRollNumber()
{
return RollNumber;
}
// integer class is immutable, this can be served directly
public String getName()
{
return Name;
}
//date class is mutable so we do not providing direct object, instead we providing a copy of it
public Date getCurrentDate()
{
return new Date(CurrentDate.getTime());
}
}
StudentImmutable.java
import java.util.Date;
public class StudentImmutable
{
public static void main(String args[]) // getting an instance of immutable class
{
Student std=Student.getInstance(101,"Rakesh", new Date());
System.out.println("RollNumber:"+ std.getRollNumber() +" Name:"+ std.getName() +" Date:"+std.getCurrentDate());
// if you try to modify the object's state then
// std.getRollNumber()=5; not permitted
// std.getName()="Rahul" not permitted
std.getCurrentDate().setDate(3);
// printing the value again to test
System.out.println("RollNumber:"+ std.getRollNumber() +" Name:"+ std.getName() +" Date:"+std.getCurrentDate());
}
}
Output