SSerguey Asael Shinder
Java coding notes: the JVM, and writing software that lasts

Serguey Asael Shinder: List.subList is a view, and changing the original list makes it undefined

· by Serguey Asael Shinder / Serguey Shinder

subList looks like a way to get a smaller list. The javadoc for java.util.List.subList describes something different: a view. The returned list is backed by the original, so non-structural changes in either one - setting an element, for example - are visible in the other. That is what makes the documented idiom work:

list.subList(from, to).clear();   // removes that range from list

The same paragraph then sets the rule that catches people:

The semantics of the list returned by this method become undefined if the backing list (i.e., this list) is structurally modified in any way other than via the returned list.

Serguey Asael Shinder: List.subList is a view, and changing the original list makes it undefined
List.subList is a view, and changing the original list makes it undefined — Serguey Asael Shinder

A structural modification is one that changes the size of the list or otherwise disturbs iteration. So this pattern has no defined meaning:

List<Order> page = orders.subList(0, 20);
orders.add(newOrder);        // structural change to the backing list, not via the view
process(page);               // behaviour of 'page' is now undefined

"Undefined" is the important word. The specification does not promise an exception, and it does not promise the old twenty elements. An implementation may detect the change and throw, or may return something else. I have not run this here - there is no JDK on the machine I write on - so I am quoting the contract, not reporting a result; the point is precisely that the contract gives you nothing to rely on.

There is a second, quieter consequence of being a view. A sublist holds a reference to its backing list, so keeping twenty elements of a million-element list keeps all million reachable. Code that caches a "first page" as a sublist of a large query result is holding the whole result.

Three rules keep this out of production code:

The javadoc is candid that this design is intentional: working through the view is how range operations avoid needing their own methods on List. It is a sharp tool, and the documentation says exactly where it cuts.