How to make a class IMMUTABLE in Java

How to make a class IMMUTABLE in Java

(Must for 5+ years experienced)

Problem Statement

We have an Employee class having following structure, and our task is to make the Employee class IMMUTABLE.

Note - Employee class is having a user defined object Address as property. Address class definition is also given below.

public class Employee {

    private int id;
    private String name;
    private Address address;
    private Date dateOfBirth;

    public Employee(){
          super();
    }
    public Employee(int id, String name, Address address, Date dateOfBirth) {
        super();
        this.id = id;
        this.name = name;
        this.address = address;
        this.dateOfBirth = dateOfBirth;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }

    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", 
address=" + address + ", dateOfBirth= " + dateOfBirth.getDate() +"/" + 
dateOfBirth.getMonth() + "/" + dateOfBirth.getYear() + "]";
    }

}
public class Address {

    int flatNo;
    String buildingName;
    String area;
    String city;

    public Address() {
        super();
    }

    public Address(int flatNo, String buildingName, String area, String city) {
        super();
        this.flatNo = flatNo;
        this.buildingName = buildingName;
        this.area = area;
        this.city = city;
    }

    public int getFlatNo() {
        return flatNo;
    }

    public void setFlatNo(int flatNo) {
        this.flatNo = flatNo;
    }

    public String getBuildingName() {
        return buildingName;
    }

    public void setBuildingName(String buildingName) {
        this.buildingName = buildingName;
    }

    public String getArea() {
        return area;
    }

    public void setArea(String area) {
        this.area = area;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address [flatNo=" + flatNo + ", buildingName=" + 
buildingName + ", area=" + area + ", city=" + city
                + "]";
    }

}

Let's create a main application to run the code.

public class ImmutabilityApplication {

    public static void main(String[] args) {

        // Create Address class object
        Address address = new Address(205, "Hiranandani Apartments",
                                     "Powai", "Mumbai");

        // Create Employee class object using the Address object 
        Employee employee = new Employee(1001, "Alice", address, 
                                                    new Date(2000, 8, 5));

        System.out.println(employee);

    }
}

If we will run the above application, we will get the below output:

Employee [id=1001, name=Alice, address=Address [flatNo=205, buildingName=Hiranandani Apartments, area=Powai, city=Mumbai], dateOfBirth= 5/8/2000]

Solution

Before we make Employee class IMMUTABLE.

Let's first understand, what' Immutable is - an immutable object is an object whose state cannot be modified after it is created.

This means, once the object of Employee class is CREATED, it's STATE shouldn't get changed in any way.

Let's see, how we can achieve this.

What we need to do, is to simply remove all the ways, which can be used to modify the object's STATE.

  • Create a single parameterized constructor only. Remove default constructor.

  • setter method : Remove all the setter methods from Employee class.

  • Restrict Extendibility : Declare class as final, so it can't be extended.

  • Restrict modification of member fields : Make member fields private and final.

Let's see, whether the above modifications are enough to make class IMMUTABLE.

Try modifying the address object's state and then run the application.

public class ImmutabilityApplication {

    public static void main(String[] args) {

        // Create Address class object
        Address address = new Address(205, "Hiranandani Apartments",
                                          "Powai", "Mumbai");

        // Create Employee class object using the Address object 
        Employee employee = new Employee(1001, "Alice", address, 
                                           new Date(2000, 8, 5));

        System.out.println("Before Address Object Modification - \n" +employee);

        // Modify Address object 
        address.setArea("Andheri East");

        System.out.println("After Address Object Modification - \n" +employee);

    }
}

Check the output below:

Employee [id=1001, name=Alice, address=Address [flatNo=205, buildingName=Hiranandani Apartments, area=Powai, city=Mumbai], dateOfBirth= 5/8/2000]

Employee [id=1001, name=Alice, address=Address [flatNo=205, buildingName=Hiranandani Apartments, area=Andheri East, city=Mumbai], dateOfBirth= 5/8/2000]

In the above output, we can clearly see that the Employee object is not IMMUTABLE, we are able to change the Employee state indirectly using Address class setArea() method.

Does this mean, we should remove the setter methods from Address class as well???

NO. Not at all. Because, we are not trying to make Address class immutable.

So, the question is, how can we stop the setter methods of Address class to modify our Immutable Employee class object.

THINK!

  • Deep Copying all the User Defined Objects as well as non primitives in Constructor.

So, after this modification, our parameterized constructor would look something like this:

public Employee(int id, String name, Address address, Date dateOfBirth) {
        super();
        this.id = id;
        this.name = name;

        // We have to Deep Copy Address class
        // By creating a new object
        this.address = new Address();
        this.address.setFlatNo(address.getFlatNo());
        this.address.setBuildingName(address.getBuildingName());
        this.address.setArea(address.getArea());
        this.address.setCity(address.getCity());

        // We have to Deep Copy Date class as well
        // As this can also be modified
        // We will only set Year, Month & Date
        this.dateOfBirth = new Date();
        this.dateOfBirth.setYear(dateOfBirth.getYear());
        this.dateOfBirth.setMonth(dateOfBirth.getMonth());
        this.dateOfBirth.setDate(dateOfBirth.getDate());
    }

Now, if you run the main application, you will get the same output. Let's run the below code.

public class ImmutabilityApplication {

    public static void main(String[] args) {

        // Create Address class object
        Address address = new Address(205, "Hiranandani Apartments",
                                          "Powai", "Mumbai");

        // Create Employee class object using the Address object 
        Employee employee = new Employee(1001, "Alice", address, 
                                            new Date(2000, 8, 5));

        System.out.println(employee);

        // Modify Address object 
        address.setArea("Andheri East");

        System.out.println(employee);

        // There's no impact on Employee object.
    }
}

Output:

Employee [id=1001, name=Alice, address=Address [flatNo=205, buildingName=Hiranandani Apartments, area=Powai, city=Mumbai], dateOfBirth= 5/8/2000]

Employee [id=1001, name=Alice, address=Address [flatNo=205, buildingName=Hiranandani Apartments, area=Powai, city=Mumbai], dateOfBirth= 5/8/2000]

Again, I have same question. Is our Employee class IMMUTABLE now???

THINK...

THINK AGAIN!!!

Still, the Employee class is not IMMUTABLE. You may be wondering, HOW???

Let me show you in code.

public class ImmutabilityApplication {

    public static void main(String[] args) {

        // Create Address class object
        Address address = new Address(205, "Hiranandani Apartments", 
                                         "Powai", "Mumbai");

        // Create Employee class object using the Address object 
        Employee employee = new Employee(1001, "Alice", address, 
                                          new Date(2000, 8, 5));

        System.out.println(employee);

        // Modify Address object 
        address.setArea("Andheri East");

        System.out.println(employee);

        // Get the dateOfBirth of the Employee 
        Date empDOB = employee.getDateOfBirth();

        // Now, let's modify the year to 1900 in empDOB
        empDOB.setYear(1900);

        System.out.println("empDOB year is modified to 1900 ");

        // Let's see, what happens to employee Object
        System.out.println(employee);


    }
}

In above code, we have tried to the Date object from Employee's getDateOfBirth() method, and then used it's setYear() method to modify the year.

If you will run the above code, you will find that the Employee dateOfBirth field got modified.

Let's see the output yourself.

Employee [id=1001, name=Alice, address=Address [flatNo=205, buildingName=Hiranandani Apartments, area=Powai, city=Mumbai], dateOfBirth= 5/8/2000]

Employee [id=1001, name=Alice, address=Address [flatNo=205, buildingName=Hiranandani Apartments, area=Powai, city=Mumbai], dateOfBirth= 5/8/2000]

empDOB year is modified to 1900

Employee [id=1001, name=Alice, address=Address [flatNo=205, buildingName=Hiranandani Apartments, area=Powai, city=Mumbai], dateOfBirth= 5/8/1900]

Now, I think, you would have some idea, what needs to be fixed here.

It's getters (get methods).

So, what we need to do is, use the same concept of DEEP CLONING that we have used in Parameterized Constructor.

Let's see the modified version of getters below.

public Date getDateOfBirth() {

        Date tempDate = new Date();

        tempDate.setDate(dateOfBirth.getDate());
        tempDate.setMonth(dateOfBirth.getMonth());
        tempDate.setYear(dateOfBirth.getYear());

        return tempDate;
    }

Wait. Don't forget to modify getAddress() method. As Address class is mutable. It's object can also be modified.

public Address getAddress() {

        Address tempAddress = new Address();

        tempAddress.setFlatNo(address.getFlatNo());
        tempAddress.setBuildingName(address.getBuildingName());
        tempAddress.setArea(address.getArea());
        tempAddress.setCity(address.getCity());

        return tempAddress;
    }

In both the modified versions of getAddress() and getDateOfBirth(), we are creating a new temporary object and returning it. In this way, we can restrict the original references to the Employee class itself.

Now, what do you think? Is our Employee class IMMUTABLE???

YESSSSS.

Summary

Let's summarize, what we did so far to make Employee class immutable.

  • Create a single parameterized constructor only. Remove default constructor.

  • setter method : Remove all the setter methods from Employee class.

  • Restrict Extendibility : Declare class as final, so it can't be extended.

  • Restrict modification of member fields : Make member fields private and final.

  • Deep Copying all the User Defined Objects as well as non primitives in Constructor.

  • Perform deep cloning of objects in the getter methods to return a copy rather than returning the actual object reference.

Complete Code

// Let's make the Employee Class Immutable
public class Employee {

    private final int id;
    private final String name;
    private final Address address;
    private final Date dateOfBirth;

    public Employee(int id, String name, Address address, Date dateOfBirth) {
        super();
        this.id = id;
        this.name = name;

        // We have to Deep Copy Address class
        // By creating a new object
        this.address = new Address();
        this.address.setFlatNo(address.getFlatNo());
        this.address.setBuildingName(address.getBuildingName());
        this.address.setArea(address.getArea());
        this.address.setCity(address.getCity());

        // We have to Deep Copy Date class as well
        // As this can also be modified
        // We will only set Year, Month & Date
        this.dateOfBirth = new Date();
        this.dateOfBirth.setYear(dateOfBirth.getYear());
        this.dateOfBirth.setMonth(dateOfBirth.getMonth());
        this.dateOfBirth.setDate(dateOfBirth.getDate());
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Address getAddress() {

        Address tempAddress = new Address();

        tempAddress.setFlatNo(address.getFlatNo());
        tempAddress.setBuildingName(address.getBuildingName());
        tempAddress.setArea(address.getArea());
        tempAddress.setCity(address.getCity());

        return tempAddress;
    }

    public Date getDateOfBirth() {

        Date tempDate = new Date();

        tempDate.setDate(dateOfBirth.getDate());
        tempDate.setMonth(dateOfBirth.getMonth());
        tempDate.setYear(dateOfBirth.getYear());

        return tempDate;
    }

    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", address=" + address + ", dateOfBirth= "
                + dateOfBirth.getDate() + "/" + dateOfBirth.getMonth() + "/" + dateOfBirth.getYear() + "]";
    }

}
public class Address {

    int flatNo;
    String buildingName;
    String area;
    String city;

    public Address() {
        super();
    }

    public Address(int flatNo, String buildingName, String area, String city) {
        super();
        this.flatNo = flatNo;
        this.buildingName = buildingName;
        this.area = area;
        this.city = city;
    }

    public int getFlatNo() {
        return flatNo;
    }

    public void setFlatNo(int flatNo) {
        this.flatNo = flatNo;
    }

    public String getBuildingName() {
        return buildingName;
    }

    public void setBuildingName(String buildingName) {
        this.buildingName = buildingName;
    }

    public String getArea() {
        return area;
    }

    public void setArea(String area) {
        this.area = area;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address [flatNo=" + flatNo + ", buildingName=" + buildingName + ", area=" + area + ", city=" + city
                + "]";
    }

}
public class ImmutabilityApplication {

    public static void main(String[] args) {

        // Create Address class object
        Address address = new Address(205, "Hiranandani Apartments", "Powai", "Mumbai");

        // Create Employee class object using the Address object 
        Employee employee = new Employee(1001, "Alice", address, new Date(2000, 8, 5));

        System.out.println(employee);

        // Modify Address object 
        address.setArea("Andheri East");

        System.out.println(employee);

        // Get the dateOfBirth of the Employee 
        Date empDOB = employee.getDateOfBirth();

        // Now, let's modify the year to 1900 in empDOB
        empDOB.setYear(1900);

        System.out.println("empDOB year is modified to 1900 ");

        // Let's see, what happens to employee Object
        System.out.println(employee);

        // Get the address of the Employee
        Address empAddress = employee.getAddress();

        // Now, modify the address
        empAddress.setCity("Delhi");

        // Print the Employee to see the IMMUTABILITY
        System.out.println(employee);


    }
}