Sharing the internals among distinct immutable objects

Sharing the internals among distinct immutable objects

Immutable objects have many advantages, e.g., they can be shared, and the sharing is thread-safe and can help avoid creating unnecessary objects. The only real disadvantage of making objects immutable is that a separate object is needed for each distinct value. This seems to be a big overhead for large immutable objects, but it is not always true. In some cases, the immutability can actually lead to a cheaper implementation than you would otherwise. The idea is that a distinct value is often not a completely different value. It is possible that some internals within immutable objects which themselves are immutable can be shared.

Suppose that we need to explode a long String into a List<String> based on a char delimiter, and there are many short Strings in the resulting List<String>. While all these short Strings surely need to be created, the creation is not so expensive because of the immutability of String.

A String is internally a not-to-be-modified char array, a begin offset and a length. When a short String is derived from the long String, we only need to change the begin offset and the length, while the char array, which is expensive to copy, can be shared. This makes creating a short String a constant time operation independent of the long String. If String was mutable, coping some or all of the elements in the char array would be inevitable.

The String.substring(int, int) method implements the above logic, and we use it to implement our explosion functionality as:

  1. public final class StringUtility {
  3.   private StringUtility() {}
  5.   public static List<String> explode(final char delimiter, final String value) {
  6.     final List<String> pieces = new LinkedList<String>();
  7.     if(null != value) {
  8.       int i = 0, offset = 0;
  9.       for(final int length = value.length(); i < length; ++i) {
  10.         if(delimiter == value.charAt(i)) {
  11.           pieces.add(value.substring(offset, i));
  12.           offset = i + 1;
  13.         }
  14.       }
  15.       pieces.add(value.substring(offset, i));
  16.     }
  17.     return pieces;
  18.   }
  19. }

Note that it will be a more expensive implementation if we try to manipulate the char array from a String and then convert it back. Because char arrays are mutable, and String needs to make defensive copies to maintain its immutability. Also note that while sharing is an efficient solution in this scenario, do not abuse it. For example, if we only want a small part of a very large String, we'd better copy instead of share to let the very large String be garbage collected.

Update: starting from JDK 1.7.0_06, String has no more the begin offset and the length. The method String.substring(int, int) always makes a copy unless the result is the same String. Nevertheless, the idea here is an important property of immutable objects and should be taken advantage of when appropriate.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.

More information about formatting options

To prevent automated spam submissions leave this field empty.