Learnitweb

Convenience Factory Methods for Collections

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 of Map, null is not allowed as key or value. Trying to create a collection with null element will result in NullPointerException.
  • 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 a Set with duplicate elements in the argument will result in IllegalArgumentException.
  • In case of Map, duplicate keys are not allowed. Trying to create a Map with duplicate keys in the argument will result in IllegalArgumentException.
  • 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 not ArrayList, for Map it is not HashMap.

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.