Posted on Leave a comment

Implementing Immutability – Access Control

6.7 Implementing Immutability

Shared resources are typically implemented using synchronized code in order to guarantee thread safety of the shared resource (§22.4, p. 1387). However, if the shared resource is an immutable object, thread safety comes for free.

An object is immutable if its state cannot be changed once it has been constructed. Since its state can only be read, there can be no thread interference and the state is always consistent.

Some examples of immutable classes from the Java SE Platform API are listed in Table 6.5. Any method that seemingly modifies the state of an immutable object is in fact returning a new immutable object with the state modified appropriately based on the original object. Primitive values are of course always immutable.

Table 6.5 Examples of Immutable Classes

Example 6.10 Implementing an Immutable Class

Click here to view code image

import java.util.Arrays;
public final class WeeklyStats {              // (1) Class is final.
  private final String description;           // (2) Immutable string
  private final int weekNumber;               // (3) Immutable primitive value
  private final int[] stats;                  // (4) Mutable int array
public WeeklyStats(String description, int weekNumber, int[] stats) {   // (5)
    if (weekNumber <= 0 || weekNumber > 52) {
      throw new IllegalArgumentException(“Invalid week number: ” + weekNumber);
    }
    if (stats.length != 7) {
      throw new IllegalArgumentException(“Stats not for whole week: ” +
                                          Arrays.toString(stats));
    }
    this.description = description;
    this.weekNumber = weekNumber;
    this.stats = Arrays.copyOf(stats, stats.length);   // Create a private copy.
  }
  public int getWeekNumber() {                // (6) Returns immutable primitive.
    return weekNumber;
  }
  public String getDescription() {            // (7) Returns immutable string.
    return description;
  }
  public int getDayStats(int dayNumber) {     // (8) Returns stats for given day.
    return (0 <= dayNumber && dayNumber < 7) ? stats[dayNumber] : -1;
  }
  public int[] getStats() {                   // (9) Returns a copy of the stats.
    return Arrays.copyOf(this.stats, this.stats.length);
  }
  @Override
  public String toString() {
    return description + “(week ” + weekNumber + “):” + Arrays.toString(stats);
  }
}

Click here to view code image

public class StatsClient {
  public static void main(String[] args) {
    WeeklyStats ws1
        = new WeeklyStats(“Appointments”, 45, new int[] {5, 3, 8, 10, 7, 8, 9});
    System.out.println(ws1);
    WeeklyStats ws2
        = new WeeklyStats(“E-mails”, 47, new int[] {10, 5, 20, 7});
    System.out.println(ws2);
  }
}

Output from the program:

Click here to view code image

Appointments(week 45):[5, 3, 8, 10, 7, 8, 9]
Exception in thread “main” java.lang.IllegalArgumentException: Stats not for whole
week: [10, 5, 20, 7]
     at WeeklyStats.<init>(WeeklyStats.java:14)
     at StatsClient.main(StatsClient.java:7)

There are certain guidelines that can help to avoid common pitfalls when implementing immutable classes. We will illustrate implementing an immutable class called WeeklyStats in Example 6.10, whose instances, once created, cannot be modified. The class WeeklyStats creates an object with weekly statistics of a specified entity.

  • It should not be possible to extend the class.

Caution should be exercised in extending an immutable class to prevent any subclass from subverting the immutable nature of the superclass.

A straightforward approach is to declare the class as final, as was done in Example 6.10 at (1) for the class WeeklyStats. Another approach is to declare the constructor as private and provide static factory methods to construct instances (discussed below). A static factory method is a static method whose sole purpose is to construct and return a new instance of the class—an alternative to calling the constructor directly.

  • All fields should be declared final and private.

Declaring the fields as private makes them accessible only inside the class, and other clients cannot access and modify them. This is the case for the fields in the WeeklyStats class at (2), (3), and (4).

Declaring a field as final means the value stored in the field cannot be changed once initialized. However, if the final field is a reference to an object, the state of this object can be changed by other clients who might be sharing this object, unless the object is also immutable. See the last guideline on how to safeguard the state of an mutable object referenced by a field.

  • Check the consistency of the object state at the time the object is created.

Since it is not possible to change the state of an immutable object, the state should be checked for consistency when the object is created. If all relevant information to initialize the object is available when it is created, the state can be checked for consistency and any necessary measures taken. For example, a suitable exception can be thrown to signal illegal arguments.

In the class WeeklyStats, the constructor at (5) is passed all the necessary values to initialize the object, and it checks whether they will result in a legal and consistent state for the object.

  • No set methods (a.k.a. setter or mutator methods) should be provided.

Set methods that change values in fields or objects referenced by fields should not be permitted. The class WeeklyStats does not have any set methods, and only provides get methods (a.k.a. getter or assessor methods).

If a setter method is necessary, then the method should create a new instance of the class based on the modified state, and return that to the client, leaving the original instance unmodified. This approach has to be weighed against the cost of creating new instances, but is usually offset by other advantages associated with using immutable classes, like thread safety without synchronized code. Caching frequently used objects can alleviate some overhead of creating new objects, as exemplified by the immutable wrapper classes for primitive types. For example, the Boolean class has a static factory method valueOf() that always returns one of two objects, Boolean.TRUE or Boolean.FALSE, depending on whether its boolean argument was true or false, respectively. The Integer class interns values between –128 and 127 for efficiency so that there is only one Integer object to represent each int value in this range.

  • A client should not be able to access mutable objects referred to by any fields in the class.

The class should not provide any methods that can modify its mutable objects. The class WeeklyStats complies with this requirement.

A class should also not share references to its mutable objects. The field at (4) has the type array of int that is mutable. An int array is passed as a parameter to the constructor at (5). The constructor in this case makes its own copy of this int array, so as not to share the array passed as an argument by the client. The getWeeklyStats() method at (8) does not return the reference value of the int array stored in the field stats. It creates and returns a new int array with values copied from its private int array. This technique is known as defensive copying. This way, the class avoids sharing references of its mutable objects with clients.

The class declaration below illustrates another approach to prevent a class from being extended. The class WeeklyStats is no longer declared final at (1), but now has a private constructor. This constructor at (5a) cannot be called by any client of the class to create an object. Instead, the class provides a static factory method at (5b) that creates an object by calling the private constructor. No subclass can be instantiated, as the superclass private constructor cannot be called, neither directly nor implicitly, in a subclass constructor.

Click here to view code image

public class WeeklyStatsV2 {                   // (1) Class is not final.
  …
  private WeeklyStatsV2(String description,
      int weekNumber, int[] stats) {           // (5a) Private constructor
    this.description = description;
    this.weekNumber = weekNumber;
    this.stats = Arrays.copyOf(stats, stats.length); // Create a private copy.
  }
  // (5b) Static factory method to construct objects.
  public static WeeklyStatsV2 getNewWeeklyStats(String description,
                                                int weekNumber, int[] stats) {
    if (weekNumber <= 0 || weekNumber > 52) {
      throw new IllegalArgumentException(“Invalid week number: ” + weekNumber);
    }
    if (stats.length != 7) {
      throw new IllegalArgumentException(“Stats not for whole week: ” +
                                         Arrays.toString(stats));
    }
    return new WeeklyStatsV2(description, weekNumber, stats);
  }
  …
}

A class having just static methods is referred to as a utility class. Such a class cannot be instantiated and has no state, and is thus immutable. Examples of such classes in the Java API include the following: the java.lang.Math class, the java.util.Collections class, the java.util.Arrays class, and the java.util.concurrent.Executors class.

Apart from being thread safe, immutable objects have many other advantages. Once created, their state is guaranteed to be consistent throughout their lifetime. That makes them easy to reason about. Immutable classes are relatively simple to construct, amenable to testing, and easy to use compared to mutable classes. There is hardly any need to make or provide provisions for making copies of such objects. Their hash code value, once computed, can be cached for later use, as it will never change. Because of their immutable state, they are ideal candidates for keys in maps, and as elements in sets. They also are ideal building blocks for new and more complex objects.

A downside of using an immutable object is that if a value must be changed in its state, then a new object must be created, which can be costly if object construction is expensive.

Leave a Reply

Your email address will not be published. Required fields are marked *