How to make a Java class immutable tutorial?
Q. What are immutable objects?
A. These are objects that cannot be modified once created.
Q. Why is it a best practice to make your classes immutable where applicable?
A.
- Immutable objects are inherently thread-safe and can be shared between threads.
- Immutable objects are good candidates for map keys and set elements as the values cannot be modified once added.
- Immutable objects are easier to construct, test, and use.
- Immutable objects will always be in a deterministic state, even when an exception is thrown.
A.
- make all fields private and final.
- don't provide any setter methods
- don't allow sub classes to override methods by making the class final.
- make sure that the other objects used are immutable as well. Defensively copy those objects.
Example 1: basic scenario
Immutable Person class
import java.util.Date; public final class Person { private final Date birthDate; public Person(Date birthDate) { //defensively copy this.birthDate = new Date(birthDate.getTime()); } public Date getBirthDate() { return new Date(birthDate.getTime()); //defensively copy } @Override public String toString() { return "Person [birthDate=" + birthDate + "]"; } }
The test class
import java.util.Calendar; import java.util.Date; public class PersonTest { public static void main(String[] args) { Calendar cal = Calendar.getInstance(); cal.set(1990,01,01,00,00,00); Date birthDate = cal.getTime(); Person person = new Person(birthDate); System.out.println(person); //if you did not defensively copy //remember java is pass by reference //mutating the birthDate will change person's dateOfBirth birthDate.setTime(new Date().getTime()); System.out.println(person); } }
Output will be
Person [birthDate=Thu Feb 01 00:00:00 EST 1990]
Person [birthDate=Thu Feb 01 00:00:00 EST 1990]
So, the above Person is an immutable class.
a) no setter method to mutate the birthDate.
b) the passed in in the constructor is birthDate is defensively copied, and not used directly.
c) The class itself is final. If it's not final then anyone could extend the class and do whatever they like, like providing setters, shadowing your private variables, and basically making it mutable.
Q. Why should we defensively copy the date in the constructor and the getter method?
A. If the reference escapes from the constructor or getter method, it can be mutated from outside as shown below since unlike a String object, the Date itself a mutable class with setter methods like setTime, setMonth, etc.
If you remove defensive copying in the constructor as shown below
import java.util.Date; public final class Person { private final Date birthDate; public Person(Date birthDate) { this.birthDate = birthDate; } public Date getBirthDate() { return birthDate; } @Override public String toString() { return "Person [birthDate=" + birthDate + "]"; } }
Now, rerun the PersonTest, and you will now get
Person [birthDate=Thu Feb 01 00:00:00 EST 1990]
Person [birthDate=Tue May 13 23:10:24 EST 2014]
The person's birthDate has been mutated.
Example 2: using the builder design pattern
It is more elegant to create immutable objects using the builder design pattern as shown below.
import java.util.Date; public final class Person { private final Date birthDate; private Person(Builder builder) { this.birthDate = builder.birthDate; } public Date getBirthDate() { return new Date(birthDate.getTime()); } @Override public String toString() { return "Person [birthDate=" + birthDate + "]"; } // builder design pattern. static inner class public static class Builder { private Date birthDate; public Builder birthDate(Date birthDate) { this.birthDate = new Date(birthDate.getTime()); return this; } public Person build() { return new Person(this); } } }
The test class
import java.util.Calendar; import java.util.Date; public class PersonTest { public static void main(String[] args) { Calendar cal = Calendar.getInstance(); cal.set(1990, 01, 01, 00, 00, 00); Date birthDate = cal.getTime(); Person person1 = new Person.Builder().birthDate(birthDate).build(); System.out.println(person1); } }
Example 3: copy write methods
If you want to get adjusted date of birth without mutating the Person, you can copy the fields and then mutate it. Make sure that the internal birthDate does not escape.
public Date getAdjustedBirthYear(int years) { Calendar cal = Calendar.getInstance(); cal.setTime(birthDate); cal.add( Calendar.YEAR, years ); return cal.getTime(); //new Date object }
Q. What is the difference between "final" and "const" keywords in Java?
A. final on a reference variable just means that the reference cannot be changed, but the actual object values can be changed if they are not immutable. "const is a reserved keyword" in Java but not used yet. const in C++ means that you actually cannot change the object itself (by calling the mutator methods)
Q. What if the Person class had a collection of children as in List<child> as an attribute?
A. Firstly, you need to deeply clone them and make sure that the children reference is immutable so that new elements cannot be added or removed.
Collections.unmodifiableList(children); //only prevents addition & removal of elements
Secondly, you need to deeply copy each element so that they cannot be modified.
public static List<Child> deepCopy(List<Child> childList) { List<Child> clonedList = new ArrayList<Child>(childList.size()); for (Child child : childList) { clonedList.add(new Child(child)); } return clonedList; }
Alternatively, use the Google's Gauva library to create immutable collections
public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of( "red", "orange", );
Labels: Core Java, Java Tutorial
0 Comments:
Post a Comment
Subscribe to Post Comments [Atom]
<< Home