A class is said to be immutable, once
you create an object for the class, you can’t change the fields of object. An
immutable object is the one that will not change its internal state after
creation. When dealing with concurrency, immutable objects are very useful,
since you can’t change the state of immutable object, you can share the object
between multiple threads without worrying.
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."
Examples of built in immutable classes in Java?
Following are some of the immutable
classes in Java.
a. String,
b. Boolean,
c. Byte,
d. Short,
e. Integer,
f. Long,
g. Float,
h. Double etc.,
Benefits of Immutable class
a. Immutable objects are inherently threading
safe, so you no need to worry about thread safety.
b. Since immutable objects state can't
change, they never get into an inconsistent state.
c. Since the properties (fields) of
immutable objects can't change, you can cache the results of the operations
performed on immutable objects and reuse the result.
d. Immutable objects are best fit to use
them as keys in HashMap.
How to create an Immutable object?
a. Declare the class as final
b. Make all of its fields as final and
private
c. Don’t expose any setter methods
d. Any fields that reference to mutable
objects such as Array, collections etc., don’t expose the data to outside the class
(by returning the reference from function), and don't change the state of
referenced data after construction of object.
a.
Declare the class as final
A class that is declared final cannot be
subclassed. If you don’t declare your class as final, then the sub class can
change the immutable behavior to mutable. Let me explain with an example.
Suppose, in my organization, I want
to increment salaries of my employees 10% on every year, no matter whether they
perform well or not.
public class Increment { private final int inc; Increment(int inc) { this.inc = inc; } public int getInc() { return inc; } }
public class MyIncrement extends Increment { private int inc; public int getInc() { return inc; } public void setInc(int inc) { this.inc = inc; } MyIncrement(int inc) { super(inc); this.inc = inc; } }
public class Employee { private String id; private double salary; Employee(String id, double salary) { this.id = id; this.salary = salary; } public String getId() { return id; } public void setId(String id) { this.id = id; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Employee [id=").append(id).append(", salary=") .append(salary).append(']'); return builder.toString(); } }
Since Increment class is not final, I
can create sub classes to Increment class. Assume I had written
processEmployee method, which takes list of employees and an Increment instance
and return all employees with updated salaries.
public
static List<Employee> processEmployee(List<Employee> emps,
Increment increment)
As you see the signature of processEmployee method, it takes list of employees and an increment object; increments the employees by the percentage specified in increment object and return employees with updated salaries. One problem here is, an Increment instance can take any Increment object (or) sub classes of Increment class. Instead of Increment instance, I can pass MyIncrement instance and change the default 10% increment behavior.
As you see the signature of processEmployee method, it takes list of employees and an increment object; increments the employees by the percentage specified in increment object and return employees with updated salaries. One problem here is, an Increment instance can take any Increment object (or) sub classes of Increment class. Instead of Increment instance, I can pass MyIncrement instance and change the default 10% increment behavior.
import java.util.Arrays; import java.util.List; import java.util.Objects; public class Test { public static List<Employee> processEmployee(List<Employee> emps, Increment increment) { if (Objects.isNull(emps) || Objects.isNull(increment)) return emps; for (Employee emp : emps) { emp.setSalary(emp.getSalary() + emp.getSalary() * increment.getInc()); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println(e.getMessage()); } } return emps; } public static void printEmployees(List<Employee> emps) { if (Objects.isNull(emps)) return; emps.stream().forEach(System.out::println); } public static void main(String[] args) throws InterruptedException { Employee emp1 = new Employee("1", 12345.67); Employee emp2 = new Employee("2", 12345.67); Employee emp3 = new Employee("3", 12345.67); Employee emp4 = new Employee("4", 12345.67); List<Employee> emps = Arrays.asList(emp1, emp2, emp3, emp4); MyIncrement increment = new MyIncrement(10); printEmployees(emps); Thread t1 = new Thread() { public void run() { processEmployee(emps, increment); } }; t1.start(); increment.setInc(20); Thread.sleep(1000); increment.setInc(30); Thread.sleep(1000); increment.setInc(40); Thread.sleep(1000); t1.join(); printEmployees(emps); } }
Sample Output
Employee [id=1, salary=12345.67] Employee [id=2, salary=12345.67] Employee [id=3, salary=12345.67] Employee [id=4, salary=12345.67] Employee [id=1, salary=259259.07] Employee [id=2, salary=382715.76999999996] Employee [id=3, salary=506172.47] Employee [id=4, salary=506172.47]
One simple solution to solve above
problem is to make the class as Final.
Do you mean, a class must be final to achieve immutability?
No, it is not necessary to make the
class final. You can make the getter methods (some times not all getter
methods, make final only those that matters to immutability) as final, so the
sub class can’t override the getter methods of sub class.
public class Increment { private final int inc; Increment(int inc) { this.inc = inc; } public final int getInc() { return inc; } }
b.
Make all of its fields as final and private
Fields declared as final are initialized
once and never change their value under normal circumstances (By using
reflections, you can change the value of final fields). One advantage of making
the field final is, to improve performance. Compilers are allowed to keep the
value of a final field cached in a register and not reload it from memory in
situations where a non-final field would have to be reloaded. Keeping the field
final emphasizes the fact that it cannot be changed anywhere else.
c.
Don’t expose any setter methods
Setter methods are used to change the state
of an object. An immutable object doesn’t change its state after its creation.
Don't provide setter methods to change the state.
d.
Any fields that reference to mutable objects such as Array, collections etc.,
don’t expose the data to outside the class.
What happen if you expose an mutable
object like List to outside. When you make an object as final, it makes the
reference pointing to that object final, not the object. So when you expose a
list kind of object, others can use it to change the state.
import java.util.List; public final class Student { private final String id; private final String name; private final List<String> hobbies; Student(String id, String name, List<String> hobbies) { this.id = id; this.name = name; this.hobbies = hobbies; } public String getId() { return id; } public String getName() { return name; } public List<String> getHobbies() { return hobbies; } }
import java.util.ArrayList; import java.util.List; import java.util.Objects; public class Test { public static void printList(List<String> hobbies) { if (Objects.isNull(hobbies)) return; hobbies.stream().forEach(System.out::println); } public static void main(String[] args) throws InterruptedException { String id = "E553"; String name = "Kiran Kumar"; List<String> hobbies = new ArrayList<>(); hobbies.add("Climbing Hills"); hobbies.add("Painting"); hobbies.add("Watching movies"); Student s1 = new Student(id, name, hobbies); System.out.println("Hobbies of student s1 are"); System.out.println("************************"); printList(s1.getHobbies()); /* Adding two more hobbies to s1 */ s1.getHobbies().add("Playing Cricket"); s1.getHobbies().add("Roaming"); System.out.println("\nHobbies of student s1 are"); System.out.println("************************"); printList(s1.getHobbies()); } }
Output
Hobbies of student s1 are ************************ Climbing Hills Painting Watching movies Hobbies of student s1 are ************************ Climbing Hills Painting Watching movies Playing Cricket Roaming
As you observe Student.java, even though
I made the List<String> hobbies ad final, I can able to add new hobbies
to the student s1 after object construction, which is violating immutability
property. So don’t expose the mutable objects directly.
How
can I expose mutable objects?
One simple way is, return the copy of
mutable object.
public List<String> getHobbies() {
return
new ArrayList<> (hobbies);
}
and one more thing you have to do in the
Student constructor. Constructor may be provided a mutable List. That way a
Student's hobbies can be modified after object construction. So Change the definition
of hobbies like below.
this.hobbies
= new ArrayList<>(hobbies);
(or)
this.hobbies =Collections.unmodifiableList(hobbies);
(or)
this.hobbies =Collections.unmodifiableList(hobbies);
import java.util.ArrayList; import java.util.List; public final class Student { private final String id; private final String name; private final List<String> hobbies; Student(String id, String name, List<String> hobbies) { this.id = id; this.name = name; this.hobbies = new ArrayList<>(hobbies); } public String getId() { return id; } public String getName() { return name; } public List<String> getHobbies() { return new ArrayList<>(hobbies); } }
Re run
Test.java, you will get following output.
Output
Hobbies of student s1 are ************************ Climbing Hills Painting Watching movies Hobbies of student s1 are ************************ Climbing Hills Painting Watching movies
I hope with above examples and
description, you got some idea about immutable object. Follow all the rules
that I specified in How to create an Immutable object? section. In real world
applications, it is easy to break the rules and render the object unsafe. So
you need to check whether an object is really immutable (or) not, by writing a
function (or) by using some third party library. You can use MutabilityDetector
library to check whether an object is immutable (or) not. MutabilityDetector
library takes your class and verify whether it satisfying all the rules of
immutability (or) not.
You may like
No comments:
Post a Comment