In the previous post in the Java 9 series, we’ve introduced a new tool in the JDK toolbox – JShell. In this post, we’ll use JShell to showcase another useful feature in Java 9 – convenience factory methods for collections.
Thanks to JEP 269, Java 9 makes it really easy to create small, immutable collections, which was relatively tedious to do with the previous JDK releases. Every Java developer knows the drill – to create a small immutable collection, let’s say a list containing 3 strings, prior to Java 9, we would do the following:
There are several issues with this approach:
- It’s verbose. It isn’t a single expression, which means static initializer blocks are needed to initialize static collection attributes.
- The collection is not really immutable, it’s more like an unmodifiable wrapper of the underlying mutable collection. If we kept a reference to the underlying collection, we could still modify it, which is why the idiom on line 13 in the snippet above is common.
- It’s expensive. The wrapper is an additional object consuming memory, and there’s performance overhead associated with mutation support in the underlying collection.
There are alternatives, of course. We could e.g.:
- Pass the elements through
Arrays.asList
:
- Use the instance-initializer construct in an anonymous inner class:
- Take advatage of the Stream API and its
Stream.of
factory method:
- Introduce an external library, such as Guava:
These alternative approaches help with verbosity, but they’re not particularly readable, require additional objects or dependencies as well as mutability support, or lack the desired flexibility (for example, there is no Arrays.asSet
method, and Stream.of
cannot be easily used to create maps).
Collection literals, syntactic constructs that evaluate to an aggregate type, would be an ideal solution to the problem. Wouldn’t an expression like the following be nice?
Unfortunately, adding new language features is hard, which is why JDK 9 introduced the next best thing – a library API consisting of convenient static factory methods on all the main collection interfaces (List
, Set
, Map
), which allow us to create truly immutable collections. For example, we can create an immutable list using the List.of
method:
The method is overloaded, quite a few times, actually. There are exactly 12 versions of it – methods for creating lists of 0-10 arguments, and a vararg method for bigger lists. While this not great for keeping the API clean, the reason we don’t have a single vararg method here is performance – there is overhead associated with allocating, initializing, and garbage collecting the array backing the vararg.
A list from our previous examples can then be created as follows:
It is truly immutable:
An interesting fact – the concrete classes returned by the convenience factory methods are not the popular ArrayList
, HashSet
, or HashMap
. They are special implementations and are not part of the public API, which means that the callers can only rely on the interfaces. This allows the implementation to change in the future. The instances are obtained via static methods on interfaces, which are not inherited, and cannot be invoked via implementing classes or instances of the interface type. This is a very nice design pattern worth pointing out.
It should also be noted that the implementations of the immutable collections are serializable, and, like all modern collections, don’t support null
elements, keys, or values.
The Set
interface offers methods very similar to List
:
And we can create a set as follows:
However, Map
is a bit different:
Like lists and sets, we have separate methods for creating maps of 0-10 entries. As keys and values are specified one after another, the methods go up to 20 arguments. Unlike for lists and sets, there is no vararg version of the Map.of
method. The reason is the fact that keys and values can be of different types, and Java doesn’t support methods with multiple varargs. To create a larger immutable map, we can take advantage of the Map.ofEntries
method accepting a single vararg of Map.Entry
:
That’s all there is to this API enhancement, really. Try it out!
Comments