Convenience Factory Methods For Collections In Java 9

In order to create an immutable collection before Java 9, a Java developer had to write code which involves creating a collection, adding elements to it and passing the collection to Collections.unmodifablexxx() method where xxx can be any collection like a set or a list. As a result, the program becomes too verbose. A Java Enhancement Proposal (jep-269) was proposed to address this and define library APIs so that read-only collections or maps with a smaller number of elements could be created without adding too many steps. These library APIs are called Convenience Factory Methods.

Why Do We Need The New Factory Methods?

The sample code given below shows how an unmodifiable list was created before Java 9.

Output for the above program will be:

The exception is thrown at the line where we try to add a new String “Omega” to unmodifiable list. Note that we had to specify List.add() method multiple times and there is a scope for improvement in the way we create the unmodifiable list. Also, note that the output of the Collections.unmodifiableList() is just a read-only view of the original list which is still modifiable and resides in the heap space.

To reduce the number of steps, we can rewrite the above program in the following ways:

In the above code, greekAlphabets list is created from an intermediate list returned from Arrays.asList() method.

In the above code, the greekAlphabets list is created using an intermediate anonymous inner class.

Java 8 introduced the concept of Streams which can be used as an input for creating the unmodifiable list:

In all the above examples, we saw that an intermediate data structure is created to finally arrive at the unmodifiable list. The intermediate data structure creation is an additional overhead and results in the creation of more number of objects than required. Moreover, if a reference is retained to the original collections involved, it is still possible to alter their contents which may affect the view returned by the static methods of java.util.Collections.

To address the problems seen in above alternate methods and significantly reduce cost, Java 9 introduced Convenience Factory Methods for Collections.

Convenience Factory Methods In Action

List.of(), Set.of() and Map.of() are overloaded convenience factory methods that were added to java.util.List, java.util.Set and java.util.Map respectively. In addition to these methods, Map.ofEntries() was also added to java.util.Map.

Let us learn more about these methods with some examples.

Creating Immutable Lists and Sets

Consider the following example:

The code demonstrates how List.of() can be used to get an immutable list. The output of the above program will be as follows:

The UnsupportedOperationException is thrown when we try to add “Omega” to the read-only list named greekAlphabets. Thus, it is confirmed that the .of() method returns an immutable list.

Similarly, it is possible to create an immutable set using Set.of() method.

Creating Immutable Maps

As mentioned earlier, we can create immutable maps using Map.of() method. One thing to note here is that we need to mention the keys and values in the order of occurrence of the entry values as the input parameters for the method. It is not possible to give an odd number of parameters for the Map.of() method as the compiler will throw an error if there is no value corresponding to a particular key.

Output for the above code will be:

An immutable map can also be created using the Map.ofEntries() method. Unlike the Map.of() method, this method accepts Map.Entry objects as input. The entries are not themselves stored in the map created using the ofEntries() method.

So, the above program can be rewritten as follows:

Output for this program will be same as the previous one.

Benefits of Using Convenience Factory Methods

The older method of using Collections.unModifiablexxx() returns an unmodifiable view of the original collection passed as an argument for the particular method. Since the view is just a wrapper over the original modifiable collection, it is still possible to modify the underlying collection if we have a reference to it. Moreover, the steps involved creates too many objects in memory, which can be unnecessary while dealing with read-only collections with smaller data-sets. The new factory methods are not like that.

Some of the  benefits of using the new methods are:

  • The resultant collections are completely serializable. Since a proxy serializable object is used in implementation classes, the implementation code can be altered in future without breaking serialization.
  • Null values are prohibited so that the implementation remain simple, compact and less number of special cases need be taken care of.
  • If we use custom objects as members of collections or maps, these custom objects should implement hashCode() and equals() methods according to the hashcode-equals contract. Otherwise, the behavior of resultant immutable collections/maps can be unpredictable.
  • After creation, the immutable collections/maps are completely thread-safe. So, these collections/maps can be used in multithreaded environments.

To Sum Up

Java 9 introduced concrete factory methods to collections framework after being inspired by EnumSet.of() method. With the introduction of these methods, it has become easier to create immutable collections or maps with smaller number of data-sets. These resultant collections or maps are better alternatives to the traditional read-only views returned by static methods in java.util.Collections as they involve lesser number of object creation, are serializable, thread-safe and showcase better performance.

Leave a Reply