Immutable classes have been a hot topic lately. The rise of functional languages who operate mostly on immutable data and the advantage of immutable data when using multiple threads (correct immutable classes are thread safe) have also created a new interest in immutable data in Java.
While some languages like Scala encourage (but do not enforce) a programming style using immutable classes, in the Java world this is less common but it can be done nonetheless. In face, many classes in the JDK are immutable, for example classes like Long
, Integer
, Double
, etc are all immutable. Also BigInteger
or BigDecimal
and of course the String
class are immutable.
Immutable classes are also more secure. For example an attacker could change the members of your classes and do bad stuff with it. For example he could subclass your classes and send an email from one of the overridden methods with private data.
In this article, I show you how to turn an ordinary mutable classes into an immutable one.
A mutable class
Let’s imagine your boss wants a new class that represents a bill for an online shop. Here is a first example of a mutable class called Bill
. This is of course not a realistic example of a real online shop. 🙂
import java.util.Date; public class Bill { private int amount; private Date date; public Bill(int amount, Date date) { this.amount = amount; this.date = date; } public int getAmount() { return amount; } public void setAmount(int amount) { this.amount = amount; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } }
In this version of the class, all the members can be changed after instances of the class have been created.
An immutable class
Let’s make this class immutable.
import org.joda.time.DateTime; public final class Bill { private final int amount; private final DateTime dateTime; public Bill(int amount, DateTime dateTime) { this.amount = amount; this.dateTime = dateTime; } public int getAmount() { return amount; } public DateTime getDateTime() { return dateTime; } }
In this example, several changes have been made to make the class immutable:
- The class is final. That means no subclasses can be created. A subclass of an immutable class can be made mutable again. As noted above, an attacker could use that to get to confidential data.
- The variables are all final and cannot be changed after construction
- In the constructor we use the
import org.joda.time.DateTime
class. This is a better version than thejava.util.Date
because it is immutable. Using ajava.util.Date
would be dangerous as it is a mutable class and we can’t control the calling thread (which might modify it). - There are no setter methods for the members.
This version of the Bill class is immutable. Now imagine your boss calls you and tells you that you need to implement another method which increased the amount of the bill after the Bill object was already created. At first you try to explain that changed the state of the class is not possible but your boss insists on this change.
You think a little bit and come up with this design of the new method
public Bill addAmount(int amount) { return new Bill(this.amount + amount, dateTime)); }
This does the trick. Instead of changing the internal state of the Bill
object and using a void method, you create a completely new Bill
object and return it. The caller of the addAmount
method will have to use this new object if he wants to use the correct bill. This is similar to methods like replace
of the String
class. They don’t really change the string on which you called the method but return a new String
object.
Using immutable collections in immutable classes
Now your boss comes again and tells you that the Bill
object must also keep a list of orders.
In order to do that, first you have to make an immutable Order
object.
public final class Order { private final int id; public Order(int id) { this.id = id; } public int getId() { return id; } }
The new version of the Bill object now looks like this:
import org.joda.time.DateTime; import com.google.common.collect.ImmutableList; public final class Bill { private final int amount; private final DateTime dateTime; private final ImmutableList<Order> orders; public Bill(int amount, DateTime dateTime, ImmutableList<Order> orders) { this.amount = amount; this.dateTime = dateTime; this.orders = orders; } public ImmutableList<Order> getOrders() { return orders; } public int getAmount() { return amount; } public DateTime getDateTime() { return dateTime; } public Bill addAmount(int amount) { return new Bill(this.amount + amount, dateTime, orders); } public Bill addOrder(Order newOrder) { ImmutableListnewOrderList = new ImmutableList.Builder () .addAll(orders).add(newOrder).build(); return new Bill(this.amount, dateTime, newOrderList); } }
This version uses a new final com.google.common.collect.ImmutableList
. This
is part of the Google Guava library where there are many different immutable collections (see here for more details: ImmutableCollectionsExplained .
The addOrder
method creates a new com.google.common.collect.ImmutableList
and then creates a new Bill object, similar to the addAmount
method.
The caller of the addOrder
method will have to use the newly returned object to use the correct Bill
instance.
Note: com.google.common.collect.ImmutableList
implements the java.util.List
interface but I normally use the com.google.common.collect.ImmutableList
in the type declaration to make it clear that I want this object to be immutable.
What about performance?
You may wonder about performance? The creation of new ones in the constructors or methods like addAmount
or addOrder
are more expensive than in a mutable class. In some situations this can be a disadvantage of your immutable classes but in most projects this probably won’t matter. To be sure, you should of course profile and test your application.
If possible immutable classes are preferred for thread safety and security. If you come from functional languages like Haskell or Lisp, this will feel very natural to you anyway. If you’ve been using mutable classes mostly, this may require some new thinking but could greatly improve your code. Of course you always have to decide for each class you develop if it makes sense.