In this tutorial, I am going to discuss what is immutable classes in java? How to create custom immutable class in java.
What is immutable class?
Immutable class means, once you created an object of a class you can’t change it’s state or attribute value. In Java, all the wrapper classes like Boolean, Short, Integer, Long, Float, Double, Byte, Char and String classes are the immutable class.
In Effective java, Joshua Bloch makes this compelling recommendation.
“Classes should be immutable unless there’s a very good reason to make them mutable….If a class cannot be made immutable, limit its mutability as much as possible.”
What’s the advantage of immutable objects?
i) Immutable classes are thread safe as state cannot be changed so no synchronization is required.
ii) Immutable objects are a good choice for keys for maps.
iii) An immutable class is good for caching purposes because you don’t have to worry about it’s state changes.
How to create immutable class with Mutable Object in java
We can make any class immutable by following these steps –
i) Make all fields/attributes private so that direct access is not allowed.
ii) Do not write any setter methods for a class. Setter methods are meant to change the state of an object which we don’t want in case of immutable class.
iii) A class must be final so that it cannot be extended. In this way, we can prevent subclasses to override methods.
iv) Add all arguments constructor for object creation.
v) Initialized all non-primitive mutable fields in a constructor by performing a deep copy.
vi) In getter methods, return the deep copy of non primitive or mutable classes rather than returning the actual reference.
To understand the above points, let’s create our own custom immutable class.
For this i am creating two classes Employee and Address. Address is the attribute of Employee class, it remain as it is. Let’s make Employee class as immutable class.
Employee class :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import java.util.List; public class Employee { private String empCode; private String name; private Address address; private List<String> hobbies; public String getEmpCode() { return empCode; } public void setEmpCode(String empCode) { this.empCode = empCode; } 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 List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } } |
Employee class has one attribute address of type Address.
Address class :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public class Address { private String streetName; private String city; private String zipCode; public Address(String streetName, String city, String zipCode) { this.streetName = streetName; this.city = city; this.zipCode = zipCode; } public String getStreetName() { return streetName; } public void setStreetName(String streetName) { this.streetName = streetName; } public void setCity(String city) { this.city = city; } public String getCity() { return city; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } public String getZipCode() { return zipCode; } } |
i) No Setters – The first step is to remove setters for each attributes so that no one can change the object field value using setter methods .
After removing setters, the Employee class have only getters method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import java.util.List; public class Employee { private String empCode; private String name; private Address address; private List<String> hobbies; public String getEmpCode() { return empCode; } public String getName() { return name; } public Address getAddress() { return address; } public List<String> getHobbies() { return hobbies; } } |
ii) Add all arguments constructor – Add all arguments constructor so that object fields can be intialized during object creation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import java.util.List; public class Employee { private String empCode; private String name; private Address address; private List<String> hobbies; public Employee(String empCode, String name, Address address, List<String> hobbies) { this.empCode = empCode; this.name = name; this.address = address; this.hobbies = hobbies; } public String getEmpCode() { return empCode; } public String getName() { return name; } public Address getAddress() { return address; } public List<String> getHobbies() { return hobbies; } } |
iii) Protect class from being extended by making as final – Make class as final so that no other classes can extend and override the methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import java.util.List; public final class Employee { private String empCode; private String name; private Address address; private List<String> hobbies; public Employee(String empCode, String name, Address address, List<String> hobbies) { this.empCode = empCode; this.name = name; this.address = address; this.hobbies = <meta charset="utf-8">hobbies; } public String getEmpCode() { return empCode; } public String getName() { return name; } public Address getAddress() { return address; } public List<String> getHobbies() { return hobbies; } } |
iv) Initialized all non-primitive mutable fields in a constructor by performing a deep copy.
By returning the deep copy ensures that no one change the mutable fields of the object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import java.util.List; public final class Employee { private String empCode; private String name; private Address address; private List<String> hobbies; public Employee(String empCode, String name, Address address, List<String> hobbies) { this.empCode = empCode; this.name = name; this.address = new Address(address.getStreetName(), address.getCity(), address.getZipCode()); this.hobbies = new ArrayList<>(hobbies); } public String getEmpCode() { return empCode; } public String getName() { return name; } public Address getAddress() { return address; } public List<String> getHobbies() { return hobbies; } } |
v) In getter methods, return the deep copy of non primitive or mutable classes rather than returning the actual reference.
If we don’t perform deep copy than the actual reference of an object is returned which can be changed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import java.util.List; public final class Employee { private String empCode; private String name; private Address address; private List<String> hobbies; public Employee(String empCode, String name, Address address, List<String> hobbies) { this.empCode = empCode; this.name = name; this.address = new Address(address.getStreetName(), address.getCity(), address.getZipCode()); this.hobbies = new ArrayList<>(hobbies); } public String getEmpCode() { return empCode; } public String getName() { return name; } public Address getAddress() { return new Address(address.getStreetName(), address.getCity(), address.getZipCode()); } public List<String> getHobbies() { return new ArrayList<>(hobbies); } } |
Let’s run it and trying to modifying the attributes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import java.util.ArrayList; import java.util.List; public class TestImmutable { public static void main(String[] args) { Address address = new Address("Test test", "Delhi", "200204"); List<String> hobbies = new ArrayList<>(); hobbies.add("Cricket"); hobbies.add("Travelling"); Employee emp = new Employee("74", "John", address, hobbies); System.out.println(emp.getName()); System.out.println(emp.getAddress().getStreetName()); System.out.println(emp.getHobbies()); System.out.println(" \n Modifying attributes \n"); emp.getAddress().setStreetName("Dummy"); emp.getHobbies().remove(0); System.out.println(emp.getName()); System.out.println(emp.getAddress().getStreetName()); System.out.println(hobbies); } } |
Output:
1 2 3 4 5 6 7 8 9 |
John Test test [Cricket, Travelling] Modifying attributes John Test test [Cricket, Travelling] |