Link

Mutable and immutable

Table of contents

  1. Mutable and immutable
  2. How can we create immutable objects?
  3. How does mutability works when we have nested objects?

Mutable and immutable

Consider the following example.

package demo;

public class App {

  public static void main( final String[] args ) {
    final Box a = new Box();

    System.out.println( "-- Initial State ----" );
    System.out.printf( "Box: %s%n", a );

    a.open();
    System.out.println( "-- Mutated State ----" );
    System.out.printf( "Box: %s%n", a );
  }
}

Output

-- Initial State ----
Box: a closed box labelled 'No Label'
-- Mutated State ----
Box: an open box labelled 'No Label'

The variable a is immutable and cannot be modified. We cannot create a new Box and assign it to the variable a or set the variable a to null.

⚠ The following example does not compile!!
package demo;

public class App {

  public static void main( final String[] args ) {
    final Box a = new Box();

    /* ⚠️ Cannot reassign!! */
    a = new Box();
  }
}

The above will not compile as variable a is marked final which means that variable a cannot change its value.

The Box object is mutable, which means we can modify its state. While variable a is final, the object to which variable a points to is mutable and thus the object can be modified.

How can we create immutable objects?

Consider the following example.

package demo;

public class Item {

  private final double weight;

  public Item( final double weight ) {
    this.weight = weight;
  }

  public double getWeight() {
    return weight;
  }

  @Override
  public String toString() {
    return String.format( "Item weighs %.4fKg", weight );
  }
}

The Item class shown above, represents an item and its weight as a property of type double, named weight. The property is final, which means that it cannot be modified once it is set.

Note that the above example has a constructor that takes the weight as its parameter. The item’s weight needs to be provided when the item is created as otherwise the weight property will not have a value.

How does mutability works when we have nested objects?

It is important to note that the objects may contain other objects. Consider the following example.

package demo;

public class Destination {

  private String department;

  public String getDepartment() {
    return department;
  }

  public void changeDepartmentTo( final String department ) {
    this.department = department;
  }

  @Override
  public String toString() {
    return String.format( "Destination: %s", department );
  }
}

The above class represents a destination where the item needs to be sent to. The department property is mutable, as it is not marked final. Consider the following updated version of the Item class.

package demo;

public class Item {

  private final double weight;
  private final Destination destination;

  public Item( final double weight, final Destination destination ) {
    this.weight = weight;
    this.destination = destination;
  }

  public double getWeight() {
    return weight;
  }

  public Destination getDestination() {
    return destination;
  }

  @Override
  public String toString() {
    return String.format( "Item weighing %.4fKg, needs to go to %s", weight, destination );
  }
}

The state defined by the Item class is immutable as both properties are marked final.

private final double weight;
private final Destination destination;

This means that we cannot change the variable values of these two properties after the object is created.

The state defined by the Destination class is mutable. This means that we can change the destination of an item even after the item is created.

package demo;

public class App {

  public static void main( final String[] args ) {
    final Destination a = new Destination();
    a.changeDepartmentTo( "Testing" );

    final Item b = new Item( 1.2, a );
    System.out.println( "-- Before changing the destination ----" );
    System.out.println( b );

    /* Change the department after creating the item */
    a.changeDepartmentTo( "Programming" );
    System.out.println( "-- After changing the destination -----" );
    System.out.println( b );
  }
}

The above example will print

-- Before changing the destination ----
Item weighing 1,2000Kg, needs to go to Destination: Testing
-- After changing the destination -----
Item weighing 1,2000Kg, needs to go to Destination: Programming

It is not recommended to mix mutable and immutable types as this may give you a false sense of security. By mistake, one may believe that the Item is immutable, when it is not. If you need to rely on mutable state within immutable objects, make use of mechanisms, such as defensive copying (discussed later on), to mitigate mutation side effects.