Fundamentals

Immutable types are not as simple as they appear at first. In fact, there are devious pitfalls with immutable types that can ensnare even the best developers. However, as long as you know where these traps lie, evading them is an easy matter. Let's start the journey by trying to create an immutable type.

Creating Immutable Types

Creating an immutable type is a simple process that takes just a minute to learn. You merely have to create a class that has no write methods; this includes property set methods as well as other methods that alter the state of the instance. See Example 3-1.

Example 3-1. An immutable person
package oracle.hcj.immutable;
public class ImmutablePerson {
 private String firstName;
 private String lastName;
 private int age;
 public ImmutablePerson(final String firstName, final String lastName, final int age) {
 if (firstName == null) {
 throw new NullPointerException("firstName"); }
 if (lastName == null) {
 throw new NullPointerException("lastName"); }
 this.age = age;
 this.firstName = firstName;
 this.lastName = lastName;
 }
 public int getAge( ) {
 return age;
 }
 public String getFirstName( ) {
 return firstName;
 }
 public String getLastName( ) {
 return lastName;
 }
}


In the ImmutablePerson class, you allow the user to pass in all arguments to the constructor and then simply don't declare any write methods to the attributes. Once the person is constructed, it looks like it can't be changed. However, unfortunately, it can be changed. This type is immutable by all appearances, but there is actually a hole.

Cracked Immutables

Using reflection, a Java developer can remove the access protection on the class and then change variable values. It is true that this wouldn't be a very smart thing to do; however, I have seen far stranger things in my career.

Screenshot

At this point, don't worry about how the access permission can be removed. We will beat that subject to death in , which covers this trick as well as many other reflection techniques.


In fact, you don't even need to use reflection to make ImmutablePerson suddenly go mutable. The attributes in the ImmutablePerson class are immutable only with respect to outside classes. This doesn't prevent an eager-to-please junior developer from writing a method into the class that ends up changing firstName to null and crashing the whole GUI code base with NullPointerExceptions. Afterwards, you will probably end up spending several hours debugging the code before it finally occurs to you that your immutable type isn't so immutable. Fortunately, there is a way you can block both of these misguided developers in one move. Using your old friend final, you can rewrite your immutable object, as shown in the following code:

package oracle.hcj.immutable;
public class ImmutablePerson {
 private final String firstName;
 private final String lastName;
 private final int age;
 public ImmutablePerson(final String firstName, final String lastName, final int age) {
 if (firstName == null) {
 throw new NullPointerException("firstName"); }
 if (lastName == null) {
 throw new NullPointerException("lastName"); }
 this.age = age;
 this.firstName = firstName;
 this.lastName = lastName;
 }
 public int getAge( ) {
 return age;
 }
 public String getFirstName( ) {
 return firstName;
 }
 public String getLastName( ) {
 return lastName;
 }
}


In the new version of ImmutablePerson, each of the attributes of the class has been declared final. This will prevent even the most enterprising developer or reflection guru from changing the attributes once they are set at construction time. Whenever you create immutable types, you should freeze the immutable variables using this technique.

False Immutable Types

Often you will encounter types that look immutable but really aren't. These objects are deceiving because they have no write methods but have the ability to change anyway. Here's an example:

package oracle.hcj.immutable;
import java.awt.Point;
public class SomeData {
 private final Point value;
 public SomeData (final Point value) {
 this.value = value;
 }
 public Point getValue( ) {
 return value;
 }
}


Although this class looks like a normal immutable type, it suffers from two gaping holes. The first hole occurs in the constructor. Since variables that contain constructed objects in Java are actually reference variables, this.value points to the same object the caller passed in. Therefore, the caller actually still has a reference to the object held in the internal variable this.value. This can be a problem if the caller has code that looks like the following:

package oracle.hcj.immutable;
import java.awt.Point;
public class SomeClass {
 public final static void main(final String[] args) {
 Point position = new Point(25, 3);
 SomeData data = new SomeData(position);
 position.x = 22;
 System.out.println(data.getValue( ));
 }
}


If you run this demo program, you will see the following results:

>ant -Dexample=oracle.hcj.immutable.SomeClass run_example run_example:
 [java] java.awt.Point[x=22,y=3]


Since the method constructing SomeData has the reference to the Point object that it passes to the constructor, it can still alter the data long after the object is constructed. The getter method suffers from a similar problem: when the Point is returned from the get method, the caller will get a reference to the same instance as the one contained in your supposedly immutable type. Also, the final keyword doesn't protect you because it will only block someone from changing the Point to which the variable position points; even as a final, the contents of the Point object stored in the position variable are changeable. The solution to this problem is to make copies of mutable objects when creating new instances of classes. Your SomeData class would be better written as:

package oracle.hcj.immutable;
import java.awt.Point;
public class SomeBetterData {
 /** Holds the value */
 private final Point value;
 public SomeBetterData(final Point value) {
 this.value = new Point(value);
 }
 public Point getValue( ) {
 return new Point(value);
 }
}


In this version of SomeData, the Point being passed to the constructor of the class is copied, and then the copy is set as the data member. This means that the caller or the constructor can change the point all they want, and the actual value in SomeBetterData won't change. With the getter, the story is similar. Whenever a user calls you to get the value, copy the Point and send back the copy. What the caller does with the copy is of no concern. Also, don't forget methods you call inside your immutable types as well. If at some point you call a method such as the following, you could have problems:

SomeClass.someMethod(this.value);


This call has the same problem as your old getter. However, the problem is slightly different: in this case, you are calling another method in another class from within the immutable object. The problem is that you are passing the reference to the internal variable to the remote method. This is dangerous because you don't know what the remote method will do with the object. In this situation, you should make a copy, just like you did for the getter:

SomeClass.someMethod(new Point(this.value));


Whenever you are creating immutable types with parameters that are not immutable, make sure that you copy all of the mutable parameters. However, if the parameters are immutable themselves, you can afford to simply store the reference to the object; since neither the class nor the caller can change the value, the object is safe. In fact, the "copy all mutable objects rule" should be applied even to types that are mutable. Whenever you allow a user of your class to have references to the objects that are internal to your class, all of your encapsulation work flies out the window. Save yourself some headaches and just copy like a demon:

package oracle.hcj.immutable;
import java.awt.Point;
public class SomeDataObject {
 private Point coordinate = new Point( );
 public void setCoordinate(final Point coordinate) {
 if (coordinate == null) {
 throw new NullPointerException("coordinate"); }
 if ((coordinate.x < 0 ) || (coordinate.y < 0)) {
 throw new IllegalArgumentException( );
 }
 this.coordinate = new Point(coordinate);
 }
 public Point getCoordinate( ) {
 return new Point(coordinate);
 }
}


Screenshot

Encapsulation is the process of exposing only the interface of a class to the users of that class, the idea being not to show any implementation details of the class.


This data object class is safely encapsulated because whenever the user calls the setCoordinate( ) method, the incoming Point object is copied before it is stored. Similarly, whenever coordinate is returned by the getCoordinate( ) method, it is copied. There is no way that the developer can break your encapsulation because he can never get a reference to the internal object.

      
Comments