Java 9 has introduced new factory methods in the collections API to make it easier for developers to create immutable collections.

In this article, I’ll first explain the motivation behind the inclusion of the new factory methods and then take you through all the methods, their usage, and implementation details.

Why does Java need a new way of creating immutable collections?

All right! Tell me, how do you create an immutable Map with some initial key-value pairs in Java 8 or less?

Well, You have to do the following -

// Instantiate a HashMap
Map<String, Integer> map = new HashMap<>();

// Put Key-Value Pairs in the Map
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);

// Obtain an unmodifiable view of the map
map = Collections.unmodifiableMap(map);

But this is too verbose, isn’t it? Can we do something else?

Well, You actually have one more option. You can use double brace initialization syntax to initialize an immutable Map like this -

Map<String, Integer> map = Collections.unmodifiableMap(
  new HashMap<String, Integer>() {{
    put("one", 1);
    put("two", 2);
    put("three", 3);
}});

This is a little less verbose but very costly. The double brace technique works by creating an anonymous inner class and providing an instance initializer block which invokes all the put() statements above.

So, every time you create a map in this way, you’re creating a non-reusable anonymous class whose object references are held by the ClassLoader. This may cause memory leaks and problems with serialization.

You can read more about double brace technique and its problems here and here.

Therefore, It is best to avoid double brace technique. So finally, we’re left with the only option of creating an empty map and then adding key-value pairs one by one.

Now, Compare the Java way of creating and initializing a Map with the Scala version -

val map = Map("one" -> 1, "two" -> 2, "three" -> 3);

And, here is the Kotlin version -

val map = mapOf("one" to 1, "two" to 2, "three" to 3);

You see how easy it is to create and initialize immutable collections in languages like Scala and Kotlin.

Java really needed a less verbose way of initializing immutable collections and therefore Java 9 has introduced static factory methods in List, Set and Map interfaces to create immutable collections.

Java 9 Factory Methods for creating immutable collections

Let’s see how the new factory methods work in Java 9.

List

Here is how you can create an immutable List using the new List.of() factory method -

List<String> animals = List.of("Cat", "Dog", "Lion", "Tiger");

Set

The Set interface also contains a similar factory method of() -

Set<String> socialMedia = Set.of("Facebook", "Twitter", "Linkedin", 
        "Pinterest", "Google+");

Map

The Map interface’s factory method accepts key-value pairs in the form of Map.of(k1, v1, k2, v2) -

Map<String, Integer> numberMap = Map.of("one", 1, "two", 2, "three", 3);

Sweet and Simple, isn’t it?

Note that the above Map.of() method can only be used to create Maps of up to 10 key-value pairs. For creating Maps of size more than 10, we have to use a different method. We’ll shortly understand the reason behind this.

A note about Implementation of the new factory methods

There is an interesting thing to note about the implementation of the new factory methods.

Although Factory methods are provided to create collections containing any number of elements, The API consists of fixed-argument overloaded versions of the of() method for creating collections of size 10 or less, and a vararg overload for creating collections of size more than 10.

For example, following are the overloaded versions of List.of() method -

List<E> List<E>.<E>of(E e1)
List<E> List<E>.<E>of(E e1, E e2)
List<E> List<E>.<E>of(E e1, E e2, E e3)
List<E> List<E>.<E>of(E e1, E e2, E e3, E e4)
List<E> List<E>.<E>of(E e1, E e2, E e3, E e4, E e5)
List<E> List<E>.<E>of(E e1, E e2, E e3, E e4, E e5, E e6)
List<E> List<E>.<E>of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)
List<E> List<E>.<E>of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)
List<E> List<E>.<E>of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)
List<E> List<E>.<E>of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)

List<E> List<E>.<E>of(E... elements)

There are 10 fixed-argument overloaded methods and a method which accepts varargs.

The fixed argument overloaded methods are provided to save the overhead of array-allocation, initialization and garbage collection in case of vararg calls.

So, for Lists of size 10 or less, the fixed-argument methods are used and for Lists of size more than 10, the vararg version of the of() method is used.

The Set interface contains the exact same set of methods.

Similarly, the Map interface also contains 10 overloaded versions of the of() factory method for creating Maps of up to 10 key-value pairs. However, for Maps of size more than 10 elements, It has a different factory method called ofEntries() -

Map<K,V> Map<K, V>.<K, V>ofEntries(Map.Entry<? extends K,? extends V>... entries)

This method accepts a varargs of Map.entry. Here is how you can use this method to create Maps of any size -

import static java.util.Map.entry;

Map<String, Integer> numerMap = Map.ofEntries(
        entry("one", 1), 
        entry("two", 2), 
        entry("three", 3)
);

Usage guidelines for the new Factory methods

1. Null values are not allowed

You can’t initialize a List, Set or Map with null values when you’re using the new factory methods -

// Throws java.lang.NullPointerException
List<String> list = List.of("A", "B", null, "C");
// Throws java.lang.NullPointerException
Set<String> set = Set.of("Computer", "Mobile", null, "TV");
// Throws java.lang.NullPointerException
Map<String, String> asciiMap = Map.of("A", "a", "B", null)

// Throws java.lang.NullPointerException
Map<String, String> map = Map.ofEntries(
    entry("A", "a"),
    entry("B", null)
)

2. Initializing a Set with Duplicate Values is not allowed

You can’t initialize a Set with duplicate values using the of() factory method -

Set<String> set = Set.of("A", "B", "A");
// java.lang.IllegalArgumentException thrown: duplicate element: A

Note, however, that this works when you create an immutable Set like this -

Set<String> set = Collections.unmodifiableSet(
        new HashSet<>(Arrays.asList("A","B","A"))
);
// Works and Produces - set ==> [A, B]

3. Initializing a Map with duplicate Keys is not allowed

You’re not allowed to add duplicate keys while initializing a Map with the new factory method -

Map.of("A", 1, "A", 2);
// java.lang.IllegalArgumentException thrown: duplicate key: A

However, If you use the old method of initializing a Map, then duplicate keys will simply be overridden -

Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("A", 2);
map = Collections.unmodifiableMap(map)
// Works and Produces - map ==> {A=2}

Conclusion

The new factory methods are a lot easier to use. They will definitely make our lives easier when working with immutable collections.

Let me know what do you think about the inclusion of these new factory methods in the comment section below.

I’ve been writing about Java 9 and its new features lately on this blog. Here are few more articles on Java 9 that you might find interesting -

Also, for more articles and updates, please subscribe to our blog’s newsletter. Thank you for reading. See you in the next post.