Core Java

Java 9 Immutable Collections Example

Hello, in this tutorial we will see another JDK 9 feature i.e. creating immutable collections in Java. Java 9 brings the long awaited approach for creating small unmodifiable Collection instances using a concise one line code. As per JEP 269, new convenience factory methods will be included in JDK 9.

1. Introduction

Before Java 9, it was possible to create an immutable view of the collections but only with some utility methods e.g. Collections.unmodifiableCollection(Collection<? extends T> c). For example, let us create an immutable view of Collection in Java 8, with a one liner. It looks bad! Isn’t it?

Test.java

Map<String, String> immutableMap = Collections.unmodifiableMap(new HashMap<String, String>() {{
       put("key1", "Value1");
       put("key2", "Value2");
       put("key3", "Value3");
}});

That’s too much code for a simple task and it should be possible to be done in a single expression. Java 9 brings now, something useful with the factory methods for creating immutable collections. Here are the examples of the factory methods:

Java Doc

// Empty Immutable Collections
List emptyImmutableList = List.of();
Set emptyImmutableSet = Set.of();
Map emptyImmutableMap = Map.of();

// Immutable Collections
List immutableList = List.of("one", "two");
Set immutableSet = Set.of("value1", "value2");
Map<String, String> immutableMap = Map.of("key1", "Value1", "key2", "Value2", "key3", "Value3");

2. Java 9 Immutable Collections Example

2.1 What are Collection Factory methods?

A collection factory method in Java is a static method that provides a simple way of initializing an immutable Collection<E>.

Being immutable, no elements can be added to, removed from, or modified inside the Collection<E> after it is initialized. With Java 9, collection factory methods are provided for the following interfaces: List<E>, Set<E> and Map<K, V>

2.2 How are they implemented?

A new package-private utility class that resides in the JDK 9 java.util.ImmutableCollections, provides multiple abstract classes that each represent a base for an immutable Collection<E>: AbstractImmutableList<E>, AbstractImmutableSet<E> and AbstractImmutableMap<K, V>.

These abstract classes are used to implement four concrete classes (except for AbstractImmutableMap<K, V> which implements three concrete classes) for each Collection<E>:

  • List<E>
  1. List0<E>: An immutable implementation of an empty List<E>
  2. List1<E>: An immutable implementation of a List<E> with one element
  3. List2<E>: An immutable implementation of a List<E> with two elements
  4. ListN<E>:An immutable implementation of a List<E> with a variable amount of elements
  • Set<E>
  1. Set0<E>: An immutable implementation of an empty Set<E>
  2. Set1<E>: An immutable implementation of a Set<E> with one element
  3. Set2<E>: An immutable implementation of a Set<E> with two elements
  4. SetN<E>: An immutable implementation of a Set<E> with a variable amount of elements
  • Map<K, V>
  1. Map0<K, V>: An immutable implementation of an empty Map<K, V>
  2. Map1<K, V>: An immutable implementation of a Map<K, V> with one key-value entry
  3. MapN<K, V>: An immutable implementation of a Map<K, V> with a variable amount of key-value entries

2.3 What do they improve?

Up until Java 9, there has been no simple universal method to initialize a Collection<E> with initial elements/key-value entries. Previously, developers were required to initialize them as follows (assuming the generic types E, K, and V have been replaced with Integer):

  • List<Integer>
    • The following method is arguably the simplest to initialize a List<Integer> with initial elements, however the result is simply a view of a List<Integer>. We are unable to add to or remove from this List<Integer>, but we are still able to modify existing elements by using List#set. For E.g.: List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    • If we wanted our List<Integer> to be entirely mutable, then we would have to pass it to the constructor of an ArrayList<Integer>, for example: List<Integer> mutableList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
  • Set<Integer>
    • A Set<Integer> required more code to initialize with initial elements than a List<Integer> does, which can be seen as: Set<Integer> mutableSet = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
  • Map<Integer, Integer>
    • A Map<Integer, Integer> is arguably the most complicated to initialize with initial key-value entries; however, there are multiple ways to go about it:
      • One method was to first initialize an empty Map<Integer, Integer> and simply call Map#put to add key-value entries
      • Another method was to use an anonymous class with two curly braces, which would still require Map#put to be called

2.4 What is the proper syntax to use?

For simplicity, we’re going to look at how to create List, Set, Map with Java 9 Factory Method for Collections.

2.4.1. List

To create a List, we use below static methods:

Java Doc

// for empty list
static <E> List<E> of()
// for list containing one element
static <E> List<E> of(E e1)
// for list containing two element
static <E> List<E> of(E e1, E e2)
// for list containing an arbitrary number of elements
static <E> List<E> of(E... elements)

For example:

Test.java

List<String> immutableList = List.of();
immutableList = List.of("one", "two", "three", null);

If we try to create list with null element, a java.lang.NullPointerException will be thrown:

Console Output

Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.Objects.requireNonNull(Objects.java:221)
	at java.base/java.util.ImmutableCollections$ListN.(ImmutableCollections.java:233)
	at java.base/java.util.List.of(List.java:859)

Because the list created with static factory method is immutable, so if we try to add an element to list, it also throws an java.lang.UnsupportedOperationException

Test.java

List<String> immutableList = List.of("one", "two", "three");
immutableList.add("four");

Console Output

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:70)
	at java.base/java.util.ImmutableCollections$AbstractImmutableList.add(ImmutableCollections.java:76)

Solution for problems above:

Test.java

List<String> mutableList = new ArrayList<String>(List.of("one", "two", "three"));
mutableList.add("four");
mutableList.add(null);

Console Output

// Result: 
[one, two, three, four, null]

2.4.2. Set

To create a Set, we use below static methods:

Java Doc

// for empty Set
static <E> Set<E> of()
// for Set containing one element
static <E> Set<E> of(E e1)
// for Set containing two element
static <E> Set<E> of(E e1, E e2)
// for Set containing an arbitrary number of elements
static <E> Set<E> of(E... elements)

For example:

Test.java

Set<String> immutableSet = Set.of();
immutableSet = Set.of("one", "two", "three", null);

If we try to create set with null element, a java.lang.NullPointerException will be thrown:

Console Output

Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.ImmutableCollections$SetN.probe(ImmutableCollections.java:520)
	at java.base/java.util.ImmutableCollections$SetN.(ImmutableCollections.java:460)
	at java.base/java.util.Set.of(Set.java:520)

Because the set created with static factory method is immutable, so if we try to add an element to set, it also throws an java.lang.UnsupportedOperationException

Test.java

Set<String> immutableSet = Set.of("one", "two", "three");
immutableSet.add("four");

Console Output

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:70)
	at java.base/java.util.ImmutableCollections$AbstractImmutableSet.add(ImmutableCollections.java:280)

Solution for problems above:

Test.java

Set<String> mutableSet = new HashSet<String>(Set.of("one", "two", "three"));
mutableSet.add("four");
mutableSet.add(null);

Console Output

// Result: 
[null, four, one, two, three]

2.4.3. Map

  • Map.of()

To create a Map, we use below static methods:

Java Doc

// for empty Map
static <K, V> Map<K, V> of()
// for Map containing a single mapping
static <K, V> Map<K, V> of(K k1, V v1)
// for Map containing two mappings
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2)
// for Map containing up to ten mappings
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10)

For example:

Test.java

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

If we try to create map with null element, a java.lang.NullPointerException will be thrown:

Console Output

Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.Objects.requireNonNull(Objects.java:221)
	at java.base/java.util.ImmutableCollections$MapN.(ImmutableCollections.java:677)
	at java.base/java.util.Map.of(Map.java:1372)

Because the map created with static factory method is immutable, so if we try to put (key, value) pair to map, it also throws an java.lang.UnsupportedOperationException

Test.java

Map<Integer, String> immutableMap = Map.of(1, "one", 2, "two", 3, "three");
immutableMap.put(4, "four");

Console Output

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:70)
	at java.base/java.util.ImmutableCollections$AbstractImmutableMap.put(ImmutableCollections.java:557)

Solution for problems above:

Test.java

Map<Integer, String> mutableMap = new HashMap<Integer, String>(Map.of(1, "one", 2, "two", 3, "three"));
mutableMap.put(4, "four");
mutableMap.put(5, null);

Console Output

// Result:
{1=one, 2=two, 3=three, 4=four, 5=null}
  • Map.ofEntries()

If we want to create a Map with more than ten mappings, there is another way: Using Map.ofEntries() method.

Java Doc

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

To use that method, we use a method for boxing keys and values, suitable for static import:

Java Doc

static <K, V> Entry<K, V> entry(K k, V v)

So, this is way to use them:

Test.java

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

2.5 Can I use collection factory methods to create mutable objects?

The Collection<E> created by collection factory methods are inherently immutable, however we are able to pass them to a constructor of an implementation of the Collection<E> to produce a mutable version i.e.

  • List<Integer>

List<Integer> mutableList = new ArrayList<>(List.of(1, 2, 3, 4, 5));

  • Set<Integer>

Set<Integer> mutableSet = new HashSet<>(Set.of(1, 2, 3, 4, 5));

  • Map<Integer, Integer>

Map<Integer, Integer> mutableMap = new HashMap<>(Map.of(1, 2, 3, 4));

3. Pitfalls of Java’s Immutable Collections

The danger of Java’s implementation is that because there is no interface specifically for immutable collections, those immutable Set and Map collections still have the mutable methods add/put and remove which will throw an UnsupportedOperationException if called.

Blankly looking at of, it isn’t obvious that the returned collection is immutable. A HashSet would be a reasonable guess since it is by far the most widely used Java set. Java’s Comparable EnumSet.of(...) returns a mutable set. More than a few runtime exceptions are going to be thrown due to of ambiguous return type.

4. Conclusion

The main goal of this article is to discuss the new collection factory methods in Java 9. Of all the new features added to Java 9, the factory method of is one of the more useful in day-to-day programming but it needs to be used with caution.

5. Download the Eclipse Project

This was an example of Java 9 Immutable Collections. Run the code and the result will be printed in the console window.

Download
You can download the full source code of this example here: Java9 Immutable Collections

Yatin

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button