Immutable Collections

  Subscribe
10/19/2006 - Marco (updated on 11/20/2017)

Java supports immutable collections of all kinds, but not in the way you would expect. A naive implementation would declare the immutable (unmodifiable in Java parlance) interface as follows1:

**interface** UnmodifiableList<T> {
  **function** T get();
  **function** int size();
}

There is no way to modify this list -- the API is simply not available. That done, we can now create the modifiable version of the list as follows:

**interface** List<T> **extends** UnmodifiableList<T> {
  **function** void add(T);
  **function** remove(T);
}

A class can now use these interfaces to carefully control access to a list as follows:

**class** SomeClass {
  **private** List<SomeOtherClass> list;

  **function** UnmodifiableList<SomeOtherClass> getList() {
    **return** list;
  }
}

That would be pretty cool, right? Unfortunately, even if you declared these interfaces yourself, the example above does not work. Java's generics support amounts to little more than syntactic sugar, so List<SomeOtherClass> does not conform to UnmodifiableList<SomeOtherClass>. There are several solutions to this problem:

  • Create a result list of the correct type and populate it with the elements of the private list
  • Perform the typecast anyway, ignoring or suppressing the error. Since Java employs erasure to transform generics when compiled, both would compile down to Array<Object> anyway.2
  • Declare a method in the List interface that returns it as an unmodifiable list. This is probably the best solution, as the code for unmodifiability will be defined in one place, the implementing collection.

So that was fun, but how exactly does it work in Java, then? In addition to the limited generics, Java is further hampered by a legacy of old code. This means that they can't (read: won't) change existing interfaces because it might break existing code. Here's how Java defines the two interfaces:

**interface** List<T> {
  **function** T get();
  **function** int size();
  **function** void add(T);
  **function** remove(T);
}

There is no second interface. All lists have methods for adding and removing elements -- even immutable ones. Immutability is enforced at run-time, not compile-time. Pretty cool, huh? Not only that, but List itself doesn't even have a method to return an unmodifiable version of itself because Sun didn't want to add methods to existing interfaces. Instead, you use a static method on the Collections class to get an immutable version of a list.

**class** SomeClass {
  **private** List<SomeOtherClass> list;

  /**
   * Returns an unmodifiable list (treat as read-only).
   */
  **function** List<SomeOtherClass> getList() {
    **return** Collections.unmodifiableList(list);
  }
}

The type system itself has nothing to say about modifiability. Any calling client can happily add elements to and remove elements from the result without any inkling that what they are doing is wrong. The compiler certainly won't tell them; the Javadoc offers the only clue -- in effect supplementing the type systems with comments! When that code is called at run-time, Java will happily issue an UnsupportedOperationException and smile smugly to itself for a job well done.

Say it with me: backwards-compatibility is king!



  1. I know this won't compile; it's Java-esque pseudo code to illustrate the idea.

  2. I'm not sure exactly which container class Java employs during erasure, so I'm using Array as a placeholder here. Using Java 1.5 and Tapestry 4.0.2

anon@69-165-175-178.dsl.teksavvy.com
11/13/2017

Making List a subclass of UnmodifiableList breaks is-a oo contract

It doesn't make logical sense to model unmodifiable and regular lists this way. Of course, it works out implementation-wise, but consider the following:

If (list instanceof UnmodifiableList) { ... }

As defined, 'List' IS-A 'UnmodifiableList' but it can also be modified. Perhaps better naming and precise declaration of the contract my help.

Sign up for our Newsletter