Now we have a template for
creating immutable objects.
- Make all fields private
- Don't provide mutators - setter ()
- Ensure that methods can't be overridden by either making the class
final (Strong Immutability) or making your methods final (Weak
Immutability)
- If a field isn't primitive or immutable, make a deep clone on the
way in and the way out.
Protect mutable fields
The last requirement which many people fall
victim too, is to build your immutable class from primitive types or
immutable fields, otherwise you have to protect mutable fields from
manipulation.
To highlight this problem, we'll use the example of a supposedly
immutable class representing a person. Our class has a first and last
name, as well as a date of birth.
import java.util.Date;
public final class BrokenPerson
{
private String firstName;
private String lastName;
private Date dob;
public BrokenPerson( String firstName,
String lastName, Date dob)
{
this.firstName = firstName;
this.lastName = lastName;
this.dob = dob;
}
public String getFirstName()
{
return this.firstName;
}
public String getLastName()
{
return this.lastName;
}
public Date getDOB()
{
return this.dob;
}
}
|
This all looks fine, until someone uses
it like this:
Date myDate = new Date();
BrokenPerson myPerson =
new BrokenPerson( "David", "O'Meara", myDate );
System.out.println( myPerson.getDOB() );
myDate.setMonth( myDate.getMonth() + 1 );
System.out.println( myPerson.getDOB() );
|
Depending
on the dates entered, the output could be something like this:
Mon Mar 24 21:34:16 GMT+08:00 2003
Thu Apr 24 21:34:16 GMT+08:00 2003
The
Date object is mutable, and the
myPerson variable is referencing the same instance of the
Date object as the
myDate variable. When
myDate changes the instance it is referencing, the
myPerson instance changes too. It isn't
immutable!
We can defend against this by taking a copy of the of the
Date instance when it is passed in rather
than trusting the reference to the instance we are given.
import java.util.Date;
public final class BetterPerson
{
private String firstName;
private String lastName;
private Date dob;
public BetterPerson( String firstName,
String lastName, Date dob)
{
this.firstName = firstName;
this.lastName = lastName;
this.dob = new Date( dob.getTime() );
}
//etc...
|
Now we're close, but we're still
not quite there. Our class is still open to abuse.
BetterPerson myPerson =
new BetterPerson( "David", "O'Meara", new Date() );
System.out.println( myPerson.getDOB() );
Date myDate = myPerson.getDOB();
myDate.setMonth( myDate.getMonth() + 1 );
System.out.println( myPerson.getDOB() );
|
We
see here that taking a copy on the way in wasn't enough; we also need to
prevent anyone from getting a reference to our mutable
Date field when we pass it out.
public Date getDOB()
{
return new Date( this.dob.getTime() );
}
|
Make deep copies of mutable data
The only point to add is that
when you copy the instance on the way in and the way out, you need to make
a deep copy. Otherwise you run the risk of leaving some mutable data in
your immutable class!
If you are confused about the need to provide a deep copy, keep in mind
that a single piece of shared mutable data, no matter how deep it is
buried inside an object, makes your class mutable. When you create a copy
of an object to defend against the value changing, you need to make sure
your copy doesn't include this shared mutable class. You need to copy any
mutable objects all the way down to the last field, and copy any nested
fields until you have a completely new copy of your own. It's the only way
to be safe!
0 comments:
Post a Comment