Wednesday, July 28, 2021

Does making all fields Final makes the class Immutable in Java? Example

One of the common misconceptions among many Java Programmers is that a class with all final fields automatically becomes Immutable. This is not correct, you can easily break the immutability of a certain class if the final field it contains is a mutable one, as we'll see in this article. One of the most common examples of this is a java.util.Date fields e.g. birthDay, expirtyDate, or joiningDate sort of fields. You have to be extra cautious to keep your class' immutability intact with mutable fields. The most common mistake in this regard happens when a Java programmer returns a reference of the original object when a client asks like getBirthDay() returns the date object pointed by birthDay field.

When you return a reference to a mutable object, you are sharing ownership of that reference with whoever receives it. This can break invariants, such as immutability.

Another example of this kind of pattern which can break immutability is returning collection or array from the getters method e.g. getListOfBooks() returns a list of books. 

Once clients get that list it can add new books, remove books and even modify employees without consulting you, hence bypassing any rules you have set up to add or remove books from that list.

So, even though, the field which is pointing to Date or Collection or array object is final, you can still break the immutability of the class by breaking Encapsulation by returning a reference to the original mutable object.



How to preserve Immutability?

There are two ways to avoid this problem, first, don't provide getters to mutable objects if you can avoid it. If you must, then consider returning a copy or clone of the mutable object. If you are returning a collection, you could wrap it as an unmodifiable collection. Since we cannot make an array final or unmodifiable in Java, I suggest avoiding returning an array, instead return an ArrayList by converting an array to ArrayList as shown here.

Now, let's see some code to understand this mistake in a little bit more detail and then we'll see the right code to solve this problem by preserving both Encapsulation and Immutability of class.

I have a simple POJO called Person which has 3 fields, name which is String, birthday which is Date, and hobbies which are in a list of String. All these fields are final, but I'll show you how you can still break the immutability of Person class by returning a reference to the mutable object to the client.

Remember, an Immutable object cannot be changed once created, hence it's value will always be same and any modification on an Immutable object should return a new Immutable object e.g. String is Immutable and when you call toUpperCase() or toLowerCase() or trim() you get a new String object.



Here is our Java Program to demonstrate that final fields are not enough to make a class Immutable in Java:

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * Java Program to demonstrate that making all
 * fields final doesn't make a class Immutable.
 * You can still break immutability if you return
 * reference to mutable objects to client.
 * 
 * @author WINDOWS 8
 *
 */
public class App {  

  public static void main(String args[]) {
     Calendar cal = Calendar.getInstance();
     cal.set(1982, 4, 21);
     Date birthDate = cal.getTime();
     
     List hobbies = new ArrayList<>();     
     hobbies.add("Painting");
     hobbies.add("Travelling");
     hobbies.add("Fitness");
    
     Person robin = new Person("Robin", birthDate, hobbies);
     System.out.println("Before");
     System.out.println(robin);
     
     // if it's immutable you can't change the object
     Date birthday = robin.getBirthday();
     birthday.setTime(System.currentTimeMillis());
     
     List originalHobbies = robin.getHobbies();
     originalHobbies.remove(0);
     originalHobbies.remove(0);
     
     System.out.println("After");
     System.out.println(robin);
     
  }

}

class Person{
  private final String name;
  private final Date birthday;
  private final List hobbies;
  
  
  public Person(String name, Date birthday, List hobbies){
    this.name = name;
    this.birthday = birthday;
    this.hobbies = hobbies;
  }
  public String getName() {
    return name;
  }
  public Date getBirthday() {
    return birthday;
  }
  public List getHobbies() {
    return hobbies;
  }
  
  @Override
  public String toString() {
    return "Person [name=" + name + ", birthday=" + birthday + ", hobbies="
        + hobbies + "]";
  }
  
  
  
}

When you run this program in your IDE or from the command line, you will see following output:

Before
Person [name=Robin, birthday=Fri May 21 10:51:13 GMT+08:00 1982,
 hobbies=[Painting, Travelling, Fitness]]
Person [name=Robin, birthday=Sun Apr 09 10:51:13 GMT+08:00 2017,
 hobbies=[Fitness]]
After

You can see that we have managed to change the state of the Person object by changing its birthdate and hobbies. This means the Person class is not Immutable even its all fields are final.  Now, let's see how we can solve this problem. If you remember, String is Immutable in Java and you cannot change its content after creating it.

Does making all fields Final makes the class Immutable in Java?



In order to preserve immutability, we need to return a clone of the mutable Date object and an unmodifiable ArrayList when the client asks for hobbies.

Here is the modified code:
class Person{
  private final String name;
  private final Date birthday;
  private final List hobbies;
  
  
  public Person(String name, Date birthday, List hobbies){
    this.name = name;
    this.birthday = birthday;
    this.hobbies = hobbies;
  }
  public String getName() {
    return name;
  }
  public Date getBirthday() {
    return (Date) birthday.clone();
  }
  public List getHobbies() {
    return Collections.unmodifiableList(hobbies);
  }
  
  @Override
  public String toString() {
    return "Person [name=" + name + ", birthday=" + birthday + ", hobbies="
        + hobbies + "]";
  }

You can see that in the getBirthDay() method, I am returning a clone of the Date object and in the getHobbies() method, I am returning and unmodifiable collection, which means if a user tries to add another hobby it will fail.

In Java, Strings are immutable but most objects are not. Whenever you have a mutator or setter method e.g. setXXX or addXXX that returns void chances are good that the object is mutable. Some of the common examples are ArrayList and LinkedList.

To make you objects immutable make sure they only have final fields that are not arrays (arrays are always mutable in java) and only return clone or copy of the mutable object from getter method if you have to, best is to avoid returning references of mutable object altogether.

That's all in this article if you are interested in learning core Java concepts like this, I suggest you read Effective Java, one of the best book for every experienced Java programmers.


7 comments:

  1. Very good..made all the concepts clear
    keep posting such things

    ReplyDelete
  2. @Nitin, Thank you, glad that you like this article.

    ReplyDelete
  3. For date, now it's time to start using java.time.*.
    I would avoid using clone like the plague...

    ReplyDelete
  4. @rvt definitely, time to use Java 8 date time API.

    ReplyDelete
  5. How about this
    birthdate.setTime(System.currentTimeMillis());

    ReplyDelete
  6. @Bullet, can you elaborate, what you are trying to do here?

    ReplyDelete