1. Introduction
JEP 269 introduced this enhancement which is the one-liner concise way of creating compact, unmodifiable collection instances with small number of elements. This enhancement of Java 9 defines library APIs to make it convenient to create instances of collections and maps with small number of elements. The purpose of these factory methods for collections is to remove the requirement of creating collection literals. The goal of this enhancement is only for small collections. The user should not expect high performance from these methods for large collections.
In this article, we’ll discuss the motivation of this enhancement, usage and implementation.
2. History and motivation
Before Java 9, the code for creating unmodifiable collection was verbose. We’ll see few methods of creating unmodifiable collection before Java 9.
2.1 Method 1
Set<String> set = new HashSet<>(); set.add("a"); set.add("b"); set.add("c"); set = Collections.unmodifiableSet(set);
This is quite verbose. We have to create an empty collection and store in local variable. We have to add elements to the collections one by one. In the end, use Collections.unmodifiableSet()
method.
We require to write so much code to create an unmodifiable collection. There was no way to achieve this in a single line or expression.
2.2 Method 2
Another way is to use the copy constructor to populate collection. For example:
Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));
This is again verbose, as one has to create a List
before creating a Set
.
2.3 Method 3
Another method to create an unmodifiable collection is to use “double brace” technique:
Set<String> set = Collections.unmodifiableSet(new HashSet<String>() {{ add("a"); add("b"); add("c"); }});
This technique uses instance-initializer construct in an anonymous inner class. In this technique, an extra class is created at each usage. It also holds hidden references to the enclosing instance and to any captured objects. This may cause memory leaks or problems with serialization. For these reasons, this technique is avoided.
2.4 Method 4
We can use Stream
API to construct small collections by combining stream factory methods and collectors. For example,
Set<String> set = Collections.unmodifiableSet(Stream.of("a", "b", "c").collect(toSet()));
Please note that the collections returned by collectors are mutable collections. Also, map can not be created with this technique unless the key and value are present in the stream.
3. Description and Usage
All three interfaces, List
, Set
and Map
, provide static method of()
for creating immutable collections. Following are important points about the generated instance by static factory method of
of List
, Set
and Map
interface:
- The generated instances are structurally immutable. That is, Elements cannot be added, removed, or replaced.
- Calling a mutator method will result in
UnsupportedOperationException
to be thrown. However, if the individual elements of the collection are mutable then the collection appear to change and cause the collection to behave inconsistently. - The
null
is not allowed. In case ofMap
,null
is not allowed as key or value. Trying to create a collection withnull
element will result inNullPointerException
. - The generated instance of collection is serializable if all elements are serializable.
- In case of
List
, the order of elements in the generated instance is same as the order of provided arguments. - In case of
Set
, the iteration order of elements is unspecified. - In case of
Map
, the iteration order of mappings is unspecified. - In case of
Set
, duplicate elements are not allowed. Trying to create aSet
with duplicate elements in the argument will result inIllegalArgumentException
. - In case of
Map
, duplicate keys are not allowed. Trying to create aMap
with duplicate keys in the argument will result inIllegalArgumentException
. - The generated instances are value based. Therefore it is not recommended to use the identity operations like using == or hash code comparison as these may return unreliable results.
- The generated instances are not commonly used implementations. That is, for
List
it is notArrayList
, forMap
it is notHashMap
.
4. Factory method of List
static <E> List<E> of() static <E> List<E> of(E e1) static <E> List<E> of(E... elements) static <E> List<E> of(E e1, E e2) static <E> List<E> of(E e1, E e2, E e3) ...................................... static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)
5. Factory method of Set
static <E> Set<E> of() static <E> Set<E> of(E e1) static <E> Set<E> of(E... elements) static <E> Set<E> of(E e1, E e2) static <E> Set<E> of(E e1, E e2, E e3) ........................................ static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)
6. Factory method of Map
static <K,V> Map<K,V> of() static <K,V> Map<K,V> of(K k1, V v1) static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2) static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3) ....................................... 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) static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)
As you can see, there are overloaded methods with 10 arguments. The purpose of providing overloaded methods is that the small collections cover most of the use cases. If the number of arguments is more than 10, then the method with var-args can be used. Every var-args method call implicitly creates array and result in garbage collection after usage. Having an overloaded method avoids this, that is why we have overloaded methods instead of having only method with var-args.
7. Examples
7.1 Generated instances are immutable
The collections created using factory methods are immutable, and changing an element, adding new elements, or removing an element throws UnsupportedOperationException
.
import java.util.List; public class Test { public static void main(String[] args) { List<String> list = List.of("a", "b", "c"); list.add("e"); } }
Output
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:147)
at Test.main(Test.java:8)
7.2 The null element is not allowed
Trying to create a collection with null
element will result in NullPointerException
.
import java.util.List; public class Test { public static void main(String[] args) { List<String> list = List.of("a", "b", "c", null); } }
Output
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:233)
at java.base/java.util.ImmutableCollections.listFromTrustedArray(ImmutableCollections.java:213)
at java.base/java.util.List.of(List.java:866)
at Test.main(Test.java:6)
7.3 Duplicate elements are not allowed while creating Set
import java.util.Set; public class Test { public static void main(String[] args) { Set<String> set = Set.of("a", "b", "c", "a"); } }
Output
Exception in thread "main" java.lang.IllegalArgumentException: duplicate element: a
at java.base/java.util.ImmutableCollections$SetN.<init>(ImmutableCollections.java:918)
at java.base/java.util.Set.of(Set.java:524)
at Test.main(Test.java:6)
7.4 Null Key or Value is not allowed while creating Set
import java.util.Map; public class Test { public static void main(String[] args) { Map<String, String> list = Map.of("a", "b", null, "d"); System.out.println(list); } }
Output
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:233)
at java.base/java.util.ImmutableCollections$MapN.<init>(ImmutableCollections.java:1184)
at java.base/java.util.Map.of(Map.java:1371)
at Test.main(Test.java:6)
7.5 Value-Based Instances
The generated instances are value based. Therefore it is not recommended to use the identity operations like using == or hash code comparison as these may return unreliable results.
import java.util.List; public class Test { public static void main(String[] args) { List<String> list1 = List.of("a", "b", "c"); List<String> list2 = List.of("a", "b", "c"); System.out.println(list1 == list2); } }
Output
false
7.6 Conclusion
In this article, we discussed convenience factory methods for collections. We discussed various important points about the usage. We also wrote few examples about the usage.