Even a chimp can write code

Thursday, November 11, 2004

A close look at immutable objects

We have all heard about immutable objects but some of us do not know what that means and fewer still know how to create one.

What does immutable really mean?
A typical object (say, a JavaBean) is mutable, i.e. it exposes mutator methods (setters) that allow you to alter its contents throughout the lifetime of the object. Immutable means non-changeable. The contents of an immutable object, once defined, cannot be altered. For e.g. in Java the String, Exception, URL, Character, Byte, Integer, Short, Long, Float and Double classes are all immutable.

Why do we need immutable objects?
Immutable objects give you thread safety. These objects can be shared by multiple threads without any fear that their content would change unbeknownst to you. That conflict-free demeanor makes them perfect ingredients for caches and even constants. Let's take the String class as an example. In some languages and APIs (which shall go nameless here to protect the guilty), you have the practice of passing an array of characters. While inside a method (or function) that received such an object as argument, you have to copy it to a local variable for fear that the contents may change without your knowledge. You also cannot make an assumption about the lifetime of that object. This results in an unnecessary replication of the object because you don't own it in the first place. With immutable objects, as Mr Gosling has said, "the question of ownership, who has the right to change it, doesn't exist.

One of the things that forced Strings to be immutable was security. You have a file open method. You pass a String to it. And then it's doing all kind of authentication checks before it gets around to doing the OS call. If you manage to do something that effectively mutated the String, after the security check and before the OS call, then boom, you're in. But Strings are immutable, so that kind of attack doesn't work. That precise example is what really demanded that Strings be immutable."

How do you build an immutable object?
...by starting with an immutable class [surprise, surprise!]. Don't sweat, details follow...

As discussed before, we can start by ensuring that the class has no setter methods. And while you're at it, make sure there are no public fields as well. You really don't want to keep a backdoor. So here it is:

// our fearless immutable class
public class StrongAndResoluteLeader {
private DomesticPolicy dp;
private ForeignPolicy fp;

public StrongAndResoluteLeader(DomesticPolicy dp,
ForeignPolicy fp) {
this.dp = dp;
this.fp = fp;
}

public DomesticPolicy getDomesticPolicy() {
return this.dp;
}

public ForeignPolicy getForeignPolicy() {
return this.fp;
}

// no mutator methods!
}


Hmm, that looks good! But if someone comes along and creates a subclass FlipFlopper, adding mutator methods like setDomesticPolicy and setForeignPolicy, it could spell doom! You could of course make the class final and close that gap as well. It would be too easy [and tempting] to call it quits at this stage. The problem is, if the DomesticPolicy and ForeignPolicy classes are not immutable themselves, we would be in more than a spot of trouble. It'd be a very blue situation indeed. Let me show you how:

DomesticPolicy dp = new DomesticPolicy();
ForeignPolicy fp = new ForeignPolicy();

StrongAndResoluteLeader w =
new StrongAndResoluteLeader(dp, fp);

// but later...
// DomesticPolicy is mutable and so we are free to do this

dp.setFiscalPlan(newEconomicPlan);


Now I'll be a monkey's uncle...! We can either make sure DomesticPolicy is immutable or be more realistic and create some darned safeguards.


// our improved fearless immutable class
public class StrongAndResoluteLeader {
private DomesticPolicy dp;
private ForeignPolicy fp;
public StrongAndResoluteLeader(DomesticPolicy dp,
ForeignPolicy fp) {

// copy the instance rather than use it directly
this.dp = new DomesticPolicy(dp.getFiscalPlan(),
dp.getSecurityPlan());
this.fp = fp;
}

public DomesticPolicy getDomesticPolicy() {
// return a deep copy, not the original
return new DomesticPolicy(this.dp.getFiscalPlan(),
this.dp.getSecurityPlan());
}

public ForeignPolicy getForeignPolicy() {
return this.fp;
}

// no mutator methods!
}


By making deep copies of the mutable DomesticPolicy instance object, we will prevent our immutable StrongAndFearlessLeader class from straying off the beaten path. This is how true immutable objects are made.

Email this | Bookmark this

3 Comments:

  • Immutable objects aren't about thread safety, though that's a side effect. It's about solving something called the "aliasing problem".

    Here's an example: I have a reference to a Wallet object. It contains several Dollar objects. I can keep track of how many Dollar objects are in it, so I don't have to worry too much.

    I give a copy of my reference to the Wallet to my wife. She thinks "Great!" and takes some of the Dollar objects out. Then I get a big surprise later when I try to pay for my lunch. :)

    In code, you pass references to things around all the time. Quite often you don't want those references changed _without you knowing about it_. That's where immutable objects come in; you can share the same reference without concern because it CAN'T change.

    By Anonymous Anonymous, at November 11, 2004 at 7:25 PM  

  • Your point about cloning incoming mutables is well taken, but you should also declare your members "final":

    private final DomesticPolicy dp;

    This serves as an ironclad contract of immutability, as there's no question of creating a setter.

    FindBugs (http://findbugs.sourceforge.net/) will warn you about the assignment of mutable objects (such as java.util.Date) to final variables.

    By Anonymous Anonymous, at November 12, 2004 at 12:32 AM  

  • Is that not coupled too much with DomesticPolicy? Suppose the DomesticPolicy class changes, eg to include 'ImmigrationPlan'. That means you would have to change your Leader class, as you're making assumptions about what constitutes the DomesticPolicy that are no longer valid.

    Would it not be better to use something like
    this.dp = (DomesticPolicy)dp.clone();
    and rely on a proper implementation of the clone() method?

    OLiver

    By Anonymous Anonymous, at November 12, 2004 at 7:55 AM  

Post a Comment | Home | Inference: my personal blog