Skip to main content

Section 8.6 References, Aliasing, and Immutable Objects

In Java, variables whose type is a class are always references to the corresponding object. Consider the following example:

public class Foo {
  int x;
  int y;

  public static void main(String[] args) {
    Foo a = new Foo();
    a.x = 3;
    // from here on, a and b alias
    Foo b = a;
    b.x = 5;
    System.out.println(a.x);  // prints 5
    System.out.println(b.x);  // prints 5
    // from here on, a and b no longer alias
    b = new Foo();
    b.x = 3;
    System.out.println(a.x);  // prints 5
    System.out.println(b.x);  // prints 3
  }
}
Figure 8.6.1. Visualization of the aliases a and b from the example code in memory.

When, at some point in time, an object has two references \(a\) and \(b\) referring to it, we say that \(a\) and \(b\) alias (or that they are aliases). In the above example, a and b are aliases of the same object between the corresponding comments.

Subsection 8.6.1 Aliases

A benefit of using references is that they can be implemented with a compact memory footprint. The compiler typically translates them to an address to main memory, where the object's fields are located. For copying (as it also implicitly happens at a method call), only an address needs to be copied, rather than the entire object. However, multiple references to the same object at the same time can lead to program errors that are difficult to find. The problem is that mutating objects through aliases often opposes modularity: If we, for example, write a method that modifies an object \(O\text{,}\) a caller of the method needs to be aware of the fact that there may be other objects that refer to \(O\) and whether they can “handle” a potentially modified object. This awareness may require knowledge about other parts of the system and therefore is often not modular because the program's correctness then depends on the correct interplay of multiple objects (of potentially different classes).

Subsection 8.6.2 Immutable Classes

One way to avoid errors caused by aliasing is to make classes immutable. This ensures that the values of the object's fields cannot change after the object is constructed. Consequently, aliasing references cannot modify the objects anymore. Copying the reference to an immutable object is therefore conceptually equivalent to copying it by value. Java supports enforcing immutability with the keyword final:

public class Vec2 {
  private final double r, phi;

  public Vec2(double r, double phi) {
    this.r   = r;
    this.phi = phi;
  }
  ...
}

final fields of a class must receive a value in the constructor once. The Java compiler rejects any code that would modify a final field of an object at compile time. However, this immutability is also enforced for methods of the class, like the translate method in our example. For immutable classes, such a method needs to instead return a new, modified object rather than modifying the existing one:

public class Vec2 {
  ...
  public Vec2 translate(double dx, double dy) {
    return cartesian(getX() + dx, getY() + dy);
  }

  public static Vec2 cartesian(double x, double y) {
    double phi = Math.atan2(y, x);
    double r   = x / Math.cos(phi);
    return new Vec2(r, phi);
  }
}

The method cartesian here serves the purpose of preparing the arguments for the constructor call, i.e., to transform the cartesian coordinates to polar coordinates. This method is marked with the static keyword. It therefore forgoes the implicit this parameter and can be called independent of a specific Vec2 object. The static method is merely declared in the namespace of Vec2. Such a particular static method, that only creates new objects, is also called a factory method.

Subsection 8.6.3 When to Use Immutable Classes?

Which classes should be made immutable and which should not needs to be decided on a case-by-case basis. Since we need to replicate instead of modify objects when we work with immutable classes, this can lead to allocating and initializing a large number of objects. Such operations cost time and memory. A class Image that represents a graphical image is for instance not a likely candidate for immutability: Assume the image has \(1000\times 1000\) pixels and every pixel requires four bytes. A single image then requires four megabytes of space. If we want to change a pixel in such an image, it is a bad idea to copy \(999,999\) pixels to create a new image.

The following table summarizes the advantages and disadvantages of immutable vs mutable objects:

Advantage Disadvantage
mutable

Efficiency

Mutation through aliases not modular

immutable

“Feels like a value”

Potentially many objects are created

Remark 8.6.2.

Many important classes in Java are immutable and use tricky implementations to reduce the inefficiency introduced by copying objects. A prominent example is String.