Sunday, July 27, 2008

Immutable Classes

I recently had a discussion with my team regarding immutable classes. Some team members agreed that immutables simplify the development and add safety to the code, but some argued that its disadvantages outweight its advantages. Another question that popped up was when to use them and when not to use them.

Overview

Before I can look at each of the issues above with more detail, I will do a quick overview of immutables. A class is said to be immutable if the state of an object of that class can't be changed after the object is created. In Java, you achieve this by not providing any setters or, more generally, any public methods that can change the value of a field. You can reinforce the immutable condition by making all fields final. Usually you will also want to make the class itself final, so that a malicious user doesn't extend the class and adds mutability.

The fields of the an immutable class should be either immutable themselves, or not reachable from outside of the class. This is to avoid someone from modifying a private field of an immutable class. If you want to expose a non-immutable field of an immutable class through a getter, for instance, you should make a copy of the field and return the copy instead. The same thing is valid for when a mutable object is passed to the constructor of an immutable class. In this case, you need to make sure to make a copy of the object and store the copy instead.

Examples of immutable classes in Java are java.lang.String and the wrapper classes like java.lang.Double, java.lang.Integer and so on. One example of a class that is not immutable but should be is the java.util.Date class.

Advantages

The main advantage of an immutable class is that you don't need to copy objects for safety reasons. Lets say you are writing a method that receives a String as a parameter and sets it to a field of the class that implements the method. You don't need to make a safety copy of the String. Since the String class is immutable, there is no risk that someone else will change the object outside of your class. Similarly, you don't need to make a copy of the String when returning it via a getter.

But if you write a method that receives a Date parameter, or a getter that returns a Date field, you will probably want to make a safety copy of that Date, otherwise you can have nasty bugs when someone inadvertently changes the Date object that you returned.

Here is an example of a bug that can happen in this case (taken from here):

task1.setStartDate(new Date("1 Jan 98");
task2.setStartDate(task1.getTaskDate());
//then somewhere in the task class
void delay(int delayDays) {
_startDate.setDate(_startDate.getDate() + delayDays);
}

// then somewhere
task2.delay(5);
Then you find that task1's start date has changed.

Another advantage is that you get thread safety for free. Since no field can be modified, you can share objects between threads without having to synchronize the calls to the object.

Yet another advantage of immutable classes is that they automatically reduce coupling. Even if a String instance is shared among many other objects, there is no coupling because there is no shared state. One object can never affect the state of another object through this String.

Disadvantages

The main disadvantage of immutable classes is that it may be a pain in the neck to use them in iterations where they have to be constantly updated. In this case, for each iteration you have to create a new instance of the class with the updated values. This was cited in the discussion I had with my team as a reason not to use immutables.

But this problem can be easily solved by providing a companion mutable class. Back to the String example, Java provides the StringBuffer class that you should use when you have to make frequent updates to a String. But common sense dictates that the usage of the mutable class should be as localized as possible. You start with a String, then you create the StringBuffer to be updated in a loop, and at the end of the loop you convert it back to a String and get rid of the StringBuffer. You usually don't pass StringBuffers around among classes or set them as fields of other classes.

When to use them

One situation when you should always use immutable classes is when the class models a value object. Value objects are things like Strings, Dates, Numbers, Money, etc. You can have other Value Objects that are specific to your domain. Value objects should always be immutable. Martin Fowler says here: " If you are using a Value Object that is mutable, treat it like it is immutable. You may not realize why, but you will save a lot of time and money". Value Objects are a whole topic on their own, so I won't get into details here, but you can look at the links above for more information.

You don't need to go in an immutable class frenzy. Classes that model business entities shouldn't be immutable, since they usually have attributes that change over time, and there is a legitimate reason to share the same instance of a business entities between classes.

One way to identify value objects is by asking yourself if two instances of the same class with the same values are equivalent. If yes, your class is probably a value object. Two business entities may have the same values in its attributes but still represent different entities and have different database primary keys.

Conclusion

Immutable classes greatly improve the quality of your code by avoiding nasty bugs and reducing coupling. They are specially recommended for value objects, since they are passed around quite frequently. They shouldn't be used for business entities.

4 comments:

Tim said...

Great post!
It seems to me that there is a close relationship here to the flyweight pattern. If objects of a class are immutable, that means they can be shared without risk. It also means that if they are shared with some frequency, they probably SHOULD be shared to save on resources, rather than creating a lot of unnecessary duplicates.

In fact, this is exactly what Java does with the String class, as much as it can: any literal String references are converted to a single reference per unique String.

Sanjeev said...

I loved the idea of Companion class. Thanks. Sanjeev

Rajeev Kumar said...

Great Post!!!

vishal said...

thanks..