An object is immutable if its state cannot change after construction. Immutable objects don’t expose any way for other objects to modify their state; the object’s fields are initialized only once inside the constructor and never change again.
Some Popular Immutable Classes
String is the most popular immutable class in Java. Once initialized its value cannot be modified. Operations like trim(), substring(), replace() always return a new instance and don’t affect the current instance, that’s why we usually call trim() as the following:
String alex = "Alex";
alex = alex.trim();
Another example from JDK is the wrapper classes like: Integer, Float, Boolean … these classes don’t modify their state, however they create a new instance each time you try to modify them.
Integer a =3;
a += 3;
After calling a += 3, a new instance is created holding the value: 6 and the first instance is lost.
In order to create an immutable class, you should follow the below steps:
Make your class final, so that no other classes can extend it.
Make all your fields final, so that they’re initialized only once inside the constructor and never modified afterward.
Don’t expose setter methods.
When exposing methods which modify the state of the class, you must always return a new instance of the class.
If the class holds a mutable object:
Inside the constructor, make sure to use a clone copy of the passed argument and never set your mutable field to the real instance passed through constructor, this is to prevent the clients who pass the object from modifying it afterwards.
Make sure to always return a clone copy of the field and never return the real object instance.
Simple Immutable Class
Let’s follow the above steps and create our own immutable class (ImmutableStudent.java).
package com.programmer.gate.beans;
public final class ImmutableStudent {
private final int id;
private final String name;
public ImmutableStudent(int id, String name) {
this.name = name;
this.id = id;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
Passing Mutable Objects to Immutable Class
Now, let’s complicate our example a bit, we create a mutable class called Age and add it as a field to ImmutableStudent:
package com.programmer.gate.beans;
public class Age {
private int day;
private int month;
private int year;
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
}
package com.programmer.gate.beans;
public final class ImmutableStudent {
private final int id;
private final String name;
private final Age age;
public ImmutableStudent(int id, String name, Age age) {
this.name = name;
this.id = id;
this.age = age;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public Age getAge() {
return age;
}
}
So, we added a new mutable field of type Age to our immutable class and assign it as normal inside the constructor.
Let’s create a simple test class and verify that ImmutableStudent is no more immutable:
public static void main(String[] args) {
Age age = new Age();
age.setDay(1);
age.setMonth(1);
age.setYear(1992);
ImmutableStudent student = new ImmutableStudent(1, "Alex", age);
System.out.println("Alex age year before modification = " + student.getAge().getYear());
age.setYear(1993);
System.out.println("Alex age year after modification = " + student.getAge().getYear());
}
After running the above test, we get the following output:
Alex age year before modification = 1992
Alex age year after modification = 1993
We claim that ImmutableStudent is an immutable class whose state is never modified after construction, however in the above example we are able to modify the age of Alex even after constructing Alex object. If we go back to the implementation of ImmutableStudent constructor, we find that age field is being assigned to the instance of the Age argument, so whenever the referenced Age is modified outside the class, the change is reflected directly on the state of Alex.
In order to fix this and make our class again immutable, we follow step #5 from the steps that we mention above for creating an immutable class. So we modify the constructor in order to clone the passed argument of Age and use a clone instance of it.
public ImmutableStudent(int id, String name, Age age) {
this.name = name;
this.id = id;
Age cloneAge = new Age();
cloneAge.setDay(age.getDay());
cloneAge.setMonth(age.getMonth());
cloneAge.setYear(age.getYear());
this.age = cloneAge;
}
Now, if we run our test, we get the following output:
Alex age year before modification = 1992
Alex age year after modification = 1992
As you see now, the age of Alex is never affected after construction and our class is back to immutable.
3.3. Returning Mutable Objects From Immutable Class
However, our class still has a leak and is not fully immutable, let’s take the following test scenario:
public static void main(String[] args) {
Age age = new Age();
age.setDay(1);
age.setMonth(1);
age.setYear(1992);
ImmutableStudent student = new ImmutableStudent(1, "Alex", age);
System.out.println("Alex age year before modification = " + student.getAge().getYear());
student.getAge().setYear(1993);
System.out.println("Alex age year after modification = " + student.getAge().getYear());
}
Output:
Alex age year before modification = 1992
Alex age year after modification = 1993
Again according to step #4, when returning mutable fields from immutable object, you should return a clone instance of them and not the real instance of the field.
So we modify getAge() in order to return a clone of the object’s age:
public Age getAge() {
Age cloneAge = new Age();
cloneAge.setDay(this.age.getDay());
cloneAge.setMonth(this.age.getMonth());
cloneAge.setYear(this.age.getYear());
return cloneAge;
}
Now the class becomes fully immutable and provides no way or method for other objects to modify its state.
Alex age year before modification = 1992
Alex age year after modification = 1992
4. Conclusion
Immutable classes provide a lot of advantages especially when used correctly in a multi-threaded environment. The only disadvantage is that they consume more memory than the traditional class since upon each modification of them a new object is created in the memory... but, a developer should not overestimate the memory consumption as its negligible compared to the advantages provided by these type of classes.
Finally, an object is immutable if it can present only one state to the other objects, no matter how and when they call its methods. If so it’s thread safe by any definition of thread-safe.
More Info on Immutable Class
ImmutableClass.java
import java.util.Date;
/**
* Always remember that your instance variables will be either mutable or immutable.
* Identify them and return new objects with copied content for all mutable objects.
* Immutable variables can be returned safely without extra effort.
* */
public final class ImmutableClass
{
/**
* Integer class is immutable as it does not provide any setter to change its content
* */
private final Integer immutableField1;
/**
* String class is immutable as it also does not provide setter to change its content
* */
private final String immutableField2;
/**
* Date class is mutable as it provide setters to change various date/time parts
* */
private final Date mutableField;
//Default private constructor will ensure no unplanned construction of class
private ImmutableClass(Integer fld1, String fld2, Date date)
{
this.immutableField1 = fld1;
this.immutableField2 = fld2;
this.mutableField = new Date(date.getTime());
}
//Factory method to store object creation logic in single place
public static ImmutableClass createNewInstance(Integer fld1, String fld2, Date date)
{
return new ImmutableClass(fld1, fld2, date);
}
//Provide no setter methods
/**
* Integer class is immutable so we can return the instance variable as it is
* */
public Integer getImmutableField1() {
return immutableField1;
}
/**
* String class is also immutable so we can return the instance variable as it is
* */
public String getImmutableField2() {
return immutableField2;
}
/**
* Date class is mutable so we need a little care here.
* We should not return the reference of original instance variable.
* Instead a new Date object, with content copied to it, should be returned.
* */
public Date getMutableField() {
return new Date(mutableField.getTime());
}
@Override
public String toString() {
return immutableField1 +" - "+ immutableField2 +" - "+ mutableField;
}
}
Now its time to test our class:
TestMain.java
class TestMain
{
public static void main(String[] args)
{
ImmutableClass im = ImmutableClass.createNewInstance(100,"test", new Date());
System.out.println(im);
tryModification(im.getImmutableField1(),im.getImmutableField2(),im.getMutableField());
System.out.println(im);
}
private static void tryModification(Integer immutableField1, String immutableField2, Date mutableField)
{
immutableField1 = 10000;
immutableField2 = "test changed";
mutableField.setDate(10);
}
}
Program output:
Console
100 - test - Tue Oct 30 21:34:08 IST 2012
100 - test - Tue Oct 30 21:34:08 IST 2012
As it can be seen that even changing the instance variables using their references does not change their value, so the class is immutable.
Immutable classes in JDK
Apart from your written classes, JDK itself has lots of immutable classes. Given is such a list of immutable classes in Java.
String
Wrapper classes such as Integer, Long, Double etc.
Immutable collection classes such as Collections.singletonMap() etc.
java.lang.StackTraceElement
Java enums (ideally they should be)
java.util.Locale
java.util.UUID
3. Benefits of making a class immutable
Lets first identify advantages of immutable class. In Java, immutable classes are:
are simple to construct, test, and use
are automatically thread-safe and have no synchronization issues
do not need a copy constructor
do not need an implementation of clone
allow hashCode() to use lazy initialization, and to cache its return value
do not need to be copied defensively when used as a field
make good Map keys and Set elements (these objects must not change state while in the collection)
have their class invariant established once upon construction, and it never needs to be checked again
always have “failure atomicity” (a term used by Joshua Bloch) : if an immutable object throws an exception, it’s never left in an undesirable or indeterminate state
Some Popular Immutable Classes
String is the most popular immutable class in Java. Once initialized its value cannot be modified. Operations like trim(), substring(), replace() always return a new instance and don’t affect the current instance, that’s why we usually call trim() as the following:
String alex = "Alex";
alex = alex.trim();
Another example from JDK is the wrapper classes like: Integer, Float, Boolean … these classes don’t modify their state, however they create a new instance each time you try to modify them.
Integer a =3;
a += 3;
After calling a += 3, a new instance is created holding the value: 6 and the first instance is lost.
In order to create an immutable class, you should follow the below steps:
Make your class final, so that no other classes can extend it.
Make all your fields final, so that they’re initialized only once inside the constructor and never modified afterward.
Don’t expose setter methods.
When exposing methods which modify the state of the class, you must always return a new instance of the class.
If the class holds a mutable object:
Inside the constructor, make sure to use a clone copy of the passed argument and never set your mutable field to the real instance passed through constructor, this is to prevent the clients who pass the object from modifying it afterwards.
Make sure to always return a clone copy of the field and never return the real object instance.
Simple Immutable Class
Let’s follow the above steps and create our own immutable class (ImmutableStudent.java).
package com.programmer.gate.beans;
public final class ImmutableStudent {
private final int id;
private final String name;
public ImmutableStudent(int id, String name) {
this.name = name;
this.id = id;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
Passing Mutable Objects to Immutable Class
Now, let’s complicate our example a bit, we create a mutable class called Age and add it as a field to ImmutableStudent:
package com.programmer.gate.beans;
public class Age {
private int day;
private int month;
private int year;
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
}
package com.programmer.gate.beans;
public final class ImmutableStudent {
private final int id;
private final String name;
private final Age age;
public ImmutableStudent(int id, String name, Age age) {
this.name = name;
this.id = id;
this.age = age;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public Age getAge() {
return age;
}
}
So, we added a new mutable field of type Age to our immutable class and assign it as normal inside the constructor.
Let’s create a simple test class and verify that ImmutableStudent is no more immutable:
public static void main(String[] args) {
Age age = new Age();
age.setDay(1);
age.setMonth(1);
age.setYear(1992);
ImmutableStudent student = new ImmutableStudent(1, "Alex", age);
System.out.println("Alex age year before modification = " + student.getAge().getYear());
age.setYear(1993);
System.out.println("Alex age year after modification = " + student.getAge().getYear());
}
After running the above test, we get the following output:
Alex age year before modification = 1992
Alex age year after modification = 1993
We claim that ImmutableStudent is an immutable class whose state is never modified after construction, however in the above example we are able to modify the age of Alex even after constructing Alex object. If we go back to the implementation of ImmutableStudent constructor, we find that age field is being assigned to the instance of the Age argument, so whenever the referenced Age is modified outside the class, the change is reflected directly on the state of Alex.
In order to fix this and make our class again immutable, we follow step #5 from the steps that we mention above for creating an immutable class. So we modify the constructor in order to clone the passed argument of Age and use a clone instance of it.
public ImmutableStudent(int id, String name, Age age) {
this.name = name;
this.id = id;
Age cloneAge = new Age();
cloneAge.setDay(age.getDay());
cloneAge.setMonth(age.getMonth());
cloneAge.setYear(age.getYear());
this.age = cloneAge;
}
Now, if we run our test, we get the following output:
Alex age year before modification = 1992
Alex age year after modification = 1992
As you see now, the age of Alex is never affected after construction and our class is back to immutable.
3.3. Returning Mutable Objects From Immutable Class
However, our class still has a leak and is not fully immutable, let’s take the following test scenario:
public static void main(String[] args) {
Age age = new Age();
age.setDay(1);
age.setMonth(1);
age.setYear(1992);
ImmutableStudent student = new ImmutableStudent(1, "Alex", age);
System.out.println("Alex age year before modification = " + student.getAge().getYear());
student.getAge().setYear(1993);
System.out.println("Alex age year after modification = " + student.getAge().getYear());
}
Output:
Alex age year before modification = 1992
Alex age year after modification = 1993
Again according to step #4, when returning mutable fields from immutable object, you should return a clone instance of them and not the real instance of the field.
So we modify getAge() in order to return a clone of the object’s age:
public Age getAge() {
Age cloneAge = new Age();
cloneAge.setDay(this.age.getDay());
cloneAge.setMonth(this.age.getMonth());
cloneAge.setYear(this.age.getYear());
return cloneAge;
}
Now the class becomes fully immutable and provides no way or method for other objects to modify its state.
Alex age year before modification = 1992
Alex age year after modification = 1992
4. Conclusion
Immutable classes provide a lot of advantages especially when used correctly in a multi-threaded environment. The only disadvantage is that they consume more memory than the traditional class since upon each modification of them a new object is created in the memory... but, a developer should not overestimate the memory consumption as its negligible compared to the advantages provided by these type of classes.
Finally, an object is immutable if it can present only one state to the other objects, no matter how and when they call its methods. If so it’s thread safe by any definition of thread-safe.
More Info on Immutable Class
ImmutableClass.java
import java.util.Date;
/**
* Always remember that your instance variables will be either mutable or immutable.
* Identify them and return new objects with copied content for all mutable objects.
* Immutable variables can be returned safely without extra effort.
* */
public final class ImmutableClass
{
/**
* Integer class is immutable as it does not provide any setter to change its content
* */
private final Integer immutableField1;
/**
* String class is immutable as it also does not provide setter to change its content
* */
private final String immutableField2;
/**
* Date class is mutable as it provide setters to change various date/time parts
* */
private final Date mutableField;
//Default private constructor will ensure no unplanned construction of class
private ImmutableClass(Integer fld1, String fld2, Date date)
{
this.immutableField1 = fld1;
this.immutableField2 = fld2;
this.mutableField = new Date(date.getTime());
}
//Factory method to store object creation logic in single place
public static ImmutableClass createNewInstance(Integer fld1, String fld2, Date date)
{
return new ImmutableClass(fld1, fld2, date);
}
//Provide no setter methods
/**
* Integer class is immutable so we can return the instance variable as it is
* */
public Integer getImmutableField1() {
return immutableField1;
}
/**
* String class is also immutable so we can return the instance variable as it is
* */
public String getImmutableField2() {
return immutableField2;
}
/**
* Date class is mutable so we need a little care here.
* We should not return the reference of original instance variable.
* Instead a new Date object, with content copied to it, should be returned.
* */
public Date getMutableField() {
return new Date(mutableField.getTime());
}
@Override
public String toString() {
return immutableField1 +" - "+ immutableField2 +" - "+ mutableField;
}
}
Now its time to test our class:
TestMain.java
class TestMain
{
public static void main(String[] args)
{
ImmutableClass im = ImmutableClass.createNewInstance(100,"test", new Date());
System.out.println(im);
tryModification(im.getImmutableField1(),im.getImmutableField2(),im.getMutableField());
System.out.println(im);
}
private static void tryModification(Integer immutableField1, String immutableField2, Date mutableField)
{
immutableField1 = 10000;
immutableField2 = "test changed";
mutableField.setDate(10);
}
}
Program output:
Console
100 - test - Tue Oct 30 21:34:08 IST 2012
100 - test - Tue Oct 30 21:34:08 IST 2012
As it can be seen that even changing the instance variables using their references does not change their value, so the class is immutable.
Immutable classes in JDK
Apart from your written classes, JDK itself has lots of immutable classes. Given is such a list of immutable classes in Java.
String
Wrapper classes such as Integer, Long, Double etc.
Immutable collection classes such as Collections.singletonMap() etc.
java.lang.StackTraceElement
Java enums (ideally they should be)
java.util.Locale
java.util.UUID
3. Benefits of making a class immutable
Lets first identify advantages of immutable class. In Java, immutable classes are:
are simple to construct, test, and use
are automatically thread-safe and have no synchronization issues
do not need a copy constructor
do not need an implementation of clone
allow hashCode() to use lazy initialization, and to cache its return value
do not need to be copied defensively when used as a field
make good Map keys and Set elements (these objects must not change state while in the collection)
have their class invariant established once upon construction, and it never needs to be checked again
always have “failure atomicity” (a term used by Joshua Bloch) : if an immutable object throws an exception, it’s never left in an undesirable or indeterminate state
No comments:
Post a Comment