Mutable or immutable?

Techniques for defensive programming

December 6, 2002

Q: How do I create my own pair of mutable and immutable classes similar to String and StringBuffer ?

A: Immutability in a "weak" sense (for lack of a better term) means creating a temporary read-only view of otherwise modifiable data. A typical case is implementing an accessor method: one must avoid giving out writable handles to internals of the class implementation. Note that an accessor is not necessarily expected to return the same data every time it is called. The key is not letting the caller subvert the read-only access protocol in place of supported mutator methods.

Immutability in a "strong" sense means locking down a piece of data in perpetuity, such as creating an immutable object instance that cannot be changed by any code. Knowing that such an instance will never change opens doors for a host of other useful patterns. For example, when cloning an object with String fields, it is not necessary to clone them since Strings are immutable.

All these immutability flavors are invaluable for good defensive programming. And since Java does not provide much native language support in this area, developers must take matters in their own hands. Fortunately, it is not very complicated.

I illustrate a couple of patterns for separating a class API into mutable and immutable parts here.

Immutable class extended by a companion nonpublic mutable class

Consider these two classes:

package items;
public class Item
{
    public Item (int a, int b)
    {
        m_a = a;
        m_b = b;
    }
    // ACCESSORS: [all methods are final]
    
    public final int getA ()
    {
        return m_a;
    }
    
    public final int getB ()
    {
        return m_b;
    }
    
    int m_a, m_b;
}
package items;
final class MutableItem extends Item
{
    public MutableItem (int a, int b)
    {
        super (a, b);
    }
    
    // MUTATORS:
    
    public void setA (int a)
    {
        m_a = a;
    }
    
    public void setB (int b)
    {
        m_b = b;
    }
}

Item is immutable: it has no mutator methods. It is further specialized into MutableItem that adds mutators. Conceptually, this inheritance relationship makes sense: being able to read and write is of course more than just being able to read.

Anything that has a MutableItem instance fully controls its state. However, this access can be restricted to read-only by upcasting such an instance to Item, as demonstrated by this simple class:

package items;
public class ItemArray
{
    public ItemArray (int size)
    {
        m_items = new MutableItem [size];
        for (int i = 0; i < size; ++ i) m_items [i] = new MutableItem (0, 0);
    }
    public final Item getItem (int index)
    {
        return m_items [index]; // no data copying necessary
    }
    public final void setItem (int index, int a, int b)
    {
        // Update the existing item:
        m_items [index].setA (a);
        m_items [index].setB (b);
    }
    
    private final MutableItem [] m_items;
}

ItemArray.getItem() exposes a mutable object in an immutable way. Note that this method has no data copying and no new object allocation, and the overall implementation is very efficient and safe. Downcasting the result of getItem() to its mutable subclass is not possible outside the items package because the mutable class is package-private. And even when this restriction does not apply, such as in my own code in the same package, downcasting needs to be coded explicitly and will trigger a mental check: should I be doing this? This weak immutability pattern is a bit like const-correctness in C++, where an object's "const-ness" is enforced by the compiler and can be cast away with an explicit directive.

Mutable class with a convert-to-immutable-version method

The previous pattern is suitable for read-only data whose usage is short-lived (e.g., scoped to a method). It trades efficiency for a weaker form of immutability: changes in the underlying data remain visible to the client code. For stronger immutability, I can do the following: create an immutable class with a toImmutable() method that locks in the data by returning a copy of it. Consider these two classes implementing a simplified version of a sorted int vector:

package vector;
public class SortedVector
{
    public SortedVector (int capacity)
    {
        m_data = new int [capacity];
    }
    
    // ACCESSORS: [all methods are final]
    
    public final int size ()
    {
        return m_size;
    }
    public final int get (int index)
    {
        return m_data [index];
    }
    
    // package-private constructor:    
    SortedVector (MutableSortedVector item)
    {
        // Note: 'm_data' array is not cloned here and is rather shared
        // by reference with the original MutableSortedVector until
        // the next MutableSortedVector mutator method is invoked [if ever]
        
        m_data = item.m_data;
        m_size = item.m_size;
    }
    
    int [] m_data;
    int m_size;
}
package vector;
public final class MutableSortedVector extends SortedVector
{
    public MutableSortedVector (int capacity)
    {
        super (capacity);
    }
    public SortedVector toImmutable ()
    {
        m_shared = true; // set 'shared' flag
        return new SortedVector (this);
    }
    
    // MUTATORS:
    // Add a new value; keep them in increasing order:
    public void add (int value)
    {
        // [This simple implementation is O(size) and is used for
        // illustrative purposes only]
        
        if (m_size == m_data.length)
            throw new IllegalStateException ("maximum capacity reached");
        // If the data array is shared, it must be cloned before we can
        // proceed [copy-on-write]:
        if (m_shared)
        {
            m_data = (int []) m_data.clone ();
            m_shared = false; // reset 'shared' flag
        }               
        
        // Insert the new value after a simple linear scan:        
        int position;    
        for (position = 0; position < m_size; ++ position)
        {
            if (m_data [position] > value) break;
        }
        
        if (position < m_size)
            System.arraycopy (m_data, position,
                              m_data, position + 1, m_size - position);
            
        m_data [position] = value;
        ++ m_size;
    }
    
    private boolean m_shared; // if 'true', next mutator call must clone data
}

Even though both classes are public, observe how toImmutable() relies on a special package-private constructor provided by SortedVector for MutableSortedVector and nobody else. Implementing such conversion methods usually implies extra data copying and extra performance hits. But if I know that the mutable instance will always be discarded right after it has been converted, I can simply pass pieces of the internal MutableSortedVector state to the SortedVector constructor by reference to reduce overhead. Being certain of this knowledge is generally difficult, however, so I handle the issue using a traditional compromise technique called copy-on-write. Observe carefully how the mutator and toImmutable() methods use the m_shared flag to copy data only when necessary.

The implementation technique outlined above is much like the one used by String and StringBuffer companion classes in the standard Java implementation, except that the mutable StringBuffer is not an extension of the immutable String.

Vladimir Roubtsov has programmed in a variety of languages for more than 12 years, including Java since 1995. Currently, he develops enterprise software as a senior developer for Trilogy in Austin, Texas.

Learn more about this topic

Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more