Create Immutable class in java

Immutable Object: An object is known as immutable if its state can not be changed over time or we can say after object creation.

Need for Immutable Classes

In current days, most of the applications are running into multi-threading environments which results in concurrent modification problems.

Popular Immutable classes in java

All wrapper classes (java.lang.Integer, java.lang.Byte, java.lang.Character, java.lang.Short, java.lang.Boolean, java.lang.Long, java.lang.Double, java.lang.Float), String class, java.lang.StackTraceElement, java.math.BigInteger, java.math.BigDecimalall, java.io.File, java.awt.Font, java.awt.BasicStroke, java.awt.Color, java.awt.Cursor, java.util.Locale, java.util.UUID, java.net.URL, java.net.URI, java.net.Inet4Address, java.net.Inet6Address, java.net.InetSocketAddress, most subclasses of java.security.Permission except java.security.PermissionCollection and subclasses, all classes of java.time except DateTimeException and number of collection classes are immutable classes in java.

Example of String as an immutable class

public class Main {
    
    public static void main(String[] args) {
        String testString1 = "website";
        System.out.println("testString1: " + testString1);
        String testString2 = testString1 + ".com";
        System.out.println("testString2: " + testString2);
        System.out.println("testString1: " + testString1);
    }
}

Output

testString1: website
testString2: website.com 
testString1: website

Create Custom Immutable Class

You can write your own immutable class, when creating an immutable class just keep in mind that after creating an object of this class object can’t be modified. Any change in an existing object results in a new object.

  1. Make the final class so that it cannot be inherited.
  2. Object state is made up of its properties, declare all properties final. So that its property value will remain constant.
  3. Object property value can be set using setter methods, so only define getter methods for all properties.
  4. Always return a new class instance from the methods that can modify the state of the class.
  5. Use deep copy instead of shallow copy, while initializing the properties by constructor.
  6. In getter methods, always perform cloning and return the clone copy instead of the actual object reference.

Simple Example of Custom Immutable Class

final class Student{
    //declare all properties final.
    final String rollNo;	
    
    public Student(String rollNo){
        this.rollNo = rollNo;
    }
    
    //only create getter method.
    public String getRollNo() {
        return rollNo;
    }
}

public class ImmutableClassExample {
    public static void main(String args[]){
        //creating Student object. 
        Student obj = new Student("MCA/07/06");
        
        System.out.println(obj.getRollNo());
    }
}

Output

MCA/07/06

 

Mutable Objects in Immutable Class

Here, we are creating the Address class which is a mutable class, and will use the Address objects in the ImmutableEmployee class which is immutable.

Address.java:

public class Address {
    private String addressLine;
    private String city;
    private String state;
    private String pinCode;
    
    public String getAddressLine() {
        return addressLine;
    }
    public void setAddressLine(String addressLine) {
        this.addressLine = addressLine;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    public String getPinCode() {
        return pinCode;
    }
    public void setPinCode(String pinCode) {
        this.pinCode = pinCode;
    }
    
}

ImmutableEmployee.java:

public class ImmutableEmployee {
    private final String name;
    private final long id;
    private final Address address;
    
    public ImmutableEmployee(String name, long id, Address address) {
        super();
        this.name = name;
        this.id = id;
        this.address = address;
    }
    
    public String getName() {
        return name;
    }
    
    public long getId() {
        return id;
    }
    
    public Address getAddress() {
        return address;
    }
    
}

MainTest.java:

public class MainTest {
    
    public static void main(String[] args) {   
        Address address = new Address();
        address.setAddressLine("Address line 1");
        address.setCity("Test City");
        address.setState("Test State");
        address.setPinCode("123456");
        
        ImmutableEmployee immutableEmployee = new ImmutableEmployee("Roy", 10, address);
        System.out.println(immutableEmployee.getName() + 
        		"'s city before modification: " + immutableEmployee.getAddress().getCity());
        address.setCity("Modified City");
        System.out.println(immutableEmployee.getName() + 
        		"'s city after modification: " + immutableEmployee.getAddress().getCity());
    } 
}

Output

Roy's city before modification: Test City 
Roy's city after modification: Modified City

As you can see in the output, the object state is changed if we modify the mutable object property of the Immutable class object. So, as per #5, we will use deep copy instead of shallow copy when initializing the properties via the constructor.

Constructor of ImmutableEmployee class will be:

public ImmutableEmployee(String name, long id, Address address) {
    super();
    this.name = name;
    this.id = id;
    Address cloneAddress = new Address();
    cloneAddress.setAddressLine(address.getAddressLine());
    cloneAddress.setCity(address.getCity());
    cloneAddress.setState(address.getState());
    cloneAddress.setPinCode(address.getPinCode());
    this.address = cloneAddress;
}

Output with the above change

Roy's city before modification: Test City
Roy's city after modification: Test City

 

The result seems good but our class is not fully immutable yet. Change the MainTest class code with the below code and see the result.

 

Updated MainTest.java

public class MainTest {
    
    public static void main(String[] args) {   
        Address address = new Address();
        address.setAddressLine("Address line 1");
        address.setCity("Test City");
        address.setState("Test State");
        address.setPinCode("123456");
        
        ImmutableEmployee immutableEmployee = new ImmutableEmployee("Roy", 10, address);
        System.out.println(immutableEmployee.getName() + 
        		"'s city before modification: " + immutableEmployee.getAddress().getCity());
        immutableEmployee.getAddress().setCity("Modified City");
        System.out.println(immutableEmployee.getName() + 
        		"'s city after modification: " + immutableEmployee.getAddress().getCity());
    } 
}

Output with the above change

Roy's city before modification: Test City 
Roy's city after modification: Modified City

 

You can see, that the city is modified again. Now, according to #6, we have to perform cloning in the getter method and return a copy of a mutable object instead of an actual reference. Let’s update the getAddress method code of the ImmutableEmployee class.

 

Updated getAddress() method

public Address getAddress() {
    Address cloneAddress = new Address();
    cloneAddress.setAddressLine(this.address.getAddressLine());
    cloneAddress.setCity(this.address.getCity());
    cloneAddress.setState(this.address.getState());
    cloneAddress.setPinCode(this.address.getPinCode());
    return cloneAddress;
}

Output with the above change

Roy's city before modification: Test City 
Roy's city after modification: Test City

Now, our class is fully Immutable.

 

Advantages/Benefits of Immutable Class.

  1. Objects are thread-safe by default.
  2. No need to synchronize immutable objects explicitly.

Disadvantages of immutable classes.

As discussed any change in an immutable object results in a new object, hence resulting in unnecessary garbage.