November 14, 2018

How to create a Java class, whose objects or instances are immutables

This is one of the most common interview questions. If you have more than two years of professional experience in java development, then you are going to face this question. In this article, I will show you, how to create a class, which creates an immutable objects or instances. First of all, what does immutable objects or instances means?
  • It can not be mutated or modified.
  • It is going to carry the same state, until garbage collected.
  • For every modification, you will be forced to create new objects.
In this article, we will use a Java class and will call it Person. We will create a normal Java class, not an immutable class, and we will convert this mutable class into an immutable class in small steps. And I will also, ask questions in the process, which your interviewer is definitely going to ask in the interview. And, I will provide you with correct answer with satisfactory expalination. Ok, then Let’s get started….
import java.util.Date;
import java.util.List;

public class Person {
  public int age;
  public String name;
  public Date dateOfBirth;
  public List<String> nameOfFriends;
}
Q: What’s wrong with above code? Ans : All the member fields are public, so anyone can access it and modify the values. Q: So, what can we do here? Ans: We can change the modifier to private. Q: But, we won’t be able to initialize the values, and this class is useless in everyway. Ans: That’s why we also provide a public constructor to initialize the values.
import java.util.Date;
import java.util.List;

public class Person {
  private int age;
  private String name;
  private Date dateOfBirth;
  private List<String> nameOfFriends;
  
  public Person(int age, String name, Date dateOfBirth, List<String> nameOfFriends) {
    this.age = age;
    this.name = name;
    this.dateOfBirth = dateOfBirth;
    this.nameOfFriends = nameOfFriends;
  }
}
Q: How are we going to read the values from the instances. As, we can not access the values, that makes this class useless again. Ans:  We will provide getter/accessor functions to get the values. And, we will not provide any setter methods, because we are going to create an immutable class and it’s values will not change once they are initialized in the constructor.
import java.util.Date;
import java.util.List;

public class Person {
  private int age;
  private String name;
  private Date dateOfBirth;
  private List<String> nameOfFriends;
  
  public Person(int age, String name, Date dateOfBirth, List<String> nameOfFriends) {
    this.age = age;
    this.name = name;
    this.dateOfBirth = dateOfBirth;
    this.nameOfFriends = nameOfFriends;
  }

  public int getAge() {
    return age;
  }

  public String getName() {
    return name;
  }

  public Date getDateOfBirth() {
    return dateOfBirth;
  }

  public List<String> getNameOfFriends() {
    return nameOfFriends;
  }

}
Q: Look at the code below. A user passes a date object in this constructor to initialize the dateOfBirth variable. As, user has a reference to that date Object, he can change the values in the date object using the reference, see the line number 3. Then this class is not really mutable, because we are able to change it’s state. How are you going to handle that?
Date dateOfBirth = new Date(System.currentTimeMillis());
Person person = new Person(12, "Hari", dateOfBirth, null);
dateOfBirth.setTime(System.currentTimeMillis());
Ans: We will initialize the dateOfBirth instance variable with the clone of the date object passed in the constructor.
if(dateOfBirth != null) {
    this.dateOfBirth = (Date) dateOfBirth.clone();
}
Q: We have a member variable of type List. After user passed a list to this constructor, he still has list reference with him. He can add or remove elements from the list, which changes the state of Person instance. So, your class is still not immutable. How you are going to handle Collection class. For example, in the below code, I created a list of friends and passed it to the constructor. After that, I added Ajay to the friend-list and removed Shyam from that person’s friend-list. Suppose something like this happens with you on FaceBook. You will find people in your friend-list, which you didn’t add. And the people you added in your friend-list are nowhere to be found. So, how you are going to handle that?
Date dateOfBirth = new Date(System.currentTimeMillis());
List<String> nameOfFriends = Arrays.asList("Ram", "Shyam", "Ajeet");
Person person = new Person(12, "Hari", dateOfBirth, nameOfFriends);
nameOfFriends.add("Ajay");
nameOfFriends.remove("Shyam");
Ans: We will create a new collection object and copy all of the values from the passed collection to new collection. And initiate the instance variable with this new object. We will add following code in the costructor.
if(nameOfFriends != null && !nameOfFriends.isEmpty()) {
  List<String> friendList = new ArrayList<>(nameOfFriends.size());
  for(String friendName : nameOfFriends) {
            friendList.add(friendName);
  }
  this.nameOfFriends = friendList;
}
Q: From the below code, I can get the referece to the nameOfFriends list, and add and remove friends from it. Then we again have the same issue. Our class in not immutable. How you are going to handle this case?
Person person = new Person(12, "Hari", dateOfBirth, nameOfFriends);
List<String> friendList = person.getNameOfFriends();
friendList.add("Ajay");
friendList.remove("Shyam");
Ans: For that, we will have to modify the getter method. We can repeate the same process, which we did in the constructor. Each time we are going to return the nameOfFriends, we will create a new list and add all friends in this list. And return this new list to the user. And the new getter for friend-list will look like code below. We can also use java-stream to create an immutable list instance variable. You can follow this link to read about it.
public List<String> getNameOfFriends() {
  List<String> friendList  = null;
  if(nameOfFriends != null && !nameOfFriends.isEmpty()) {
    friendList = new ArrayList<>(nameOfFriends.size());
    for(String friendName : nameOfFriends) {
      friendList.add(friendName);
    }
  }
  return friendList;
}
Q: I can still get the reference to the dateOfBirth variable. And I can modify it’s values using the reference. This class is still mutable.
Person person = new Person(12, "Hari", dateOfBirth, nameOfFriends);
Date dob = person.getDateOfBirth();
dob.setTime(0);
Ans: We will have to modify the getter method for dateOfBirth variable to return a clone of the date object, instead of original object.
public Date getDateOfBirth() {
  Date dob = null;
  if(dateOfBirth != null) {
    dob = (Date) dateOfBirth.clone();
  }
  return dob;
}
We also need to add final modifier to this class. You can read about that here And the final version of class, which is really immutable will look like below.
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

final public class Person {
  private int age;
  private String name;
  private Date dateOfBirth;
  private List<String> nameOfFriends;
  
  public Person(int age, String name, Date dateOfBirth, List<String> nameOfFriends) {
    this.age = age;
    this.name = name;
    if(dateOfBirth != null) {
      this.dateOfBirth = (Date) dateOfBirth.clone();
    }
    if(nameOfFriends != null && !nameOfFriends.isEmpty()) {
      List<String> friendList = new ArrayList<>(nameOfFriends.size());
      for(String friendName : nameOfFriends) {
        friendList.add(friendName);
      }
      this.nameOfFriends = friendList;
    }
  }

  public int getAge() {
    return age;
  }

  public String getName() {
    return name;
  }

  public Date getDateOfBirth() {
    Date dob = null;
    if(dateOfBirth != null) {
      dob = (Date) dateOfBirth.clone();
    }
    return dob;
  }	

  public List<String> getNameOfFriends() {
    List<String> friendList  = null;
    if(nameOfFriends != null && !nameOfFriends.isEmpty()) {
      friendList = new ArrayList<>(nameOfFriends.size());
      for(String friendName : nameOfFriends) {
        friendList.add(friendName);
      }
    }
    return friendList;
  }
}
 

Leave a Reply

Your email address will not be published.