Google

May 14, 2014

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.
 Q. What are the guidelines to make a class immutable?
 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.
In Java API, the String class and the wrapper classes like Integer, Double, Character, etc are immutable objects. Let's look at different examples to make a class immutable like builder pattern, etc.


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: ,

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home