Convenience Factory Methods for Collections | Java9
In this article, we will see another JDK9 feature Convenience Factory Methods of Collections to create compact, unmodifiable collection instances with single line of code.
You can download JDK 1.9 from here based on your OS Platform requirements.
If you are also interested in learning other features of Java9, you can read our post Java 9 Features
Available Approaches for initializing collections till Java 8
1. Tradition approach
Java is often criticized for its verbosity. Creating a small, unmodifiable collection (say, a set) involves lots of code. For Example,
Set<String> set = new HashSet<>(); set.add("a"); set.add("b"); set.add("c"); set = Collections.unmodifiableSet(set);
This is quite verbose, and because it cannot be expressed in a single expression, static collections must be populated in static initializer blocks. For Example,
public class Test { static Set<String> set = new HashSet<>(); static { set.add("a"); set.add("b"); set.add("c"); set = Collections.unmodifiableSet(set); } // methods }
2. Using copy constructor of List interface i.e. asList() method
First approach is true for Set and Map, however, for List, there’s a factory method:
List<String> list = Arrays.asList("a", "b", "c");
Alternatively, we can populate a List,Set or Map using a copy constructor and above factory method of List. For Example,
Set<String> set = Collections.unmodifiableSet( new HashSet<>(Arrays.asList("a", "b", "c")));
This is still somewhat verbose and also little tedious since we have to create a List before creating the Set or Map.
3. Double Brace Technique
Another alternative is to use the so-called Double Brace technique:
Set<String> set = Collections.unmodifiableSet(new HashSet<String>() {{ add("a"); add("b"); add("c"); }});
This technique also has many disadvantages:
- The double brace technique is only a little less verbose but greatly reduces the readability.
- It also uses the instance-initializer construct in an anonymous inner class, which is a bit prettier. However, it is quite obscure, and it costs an extra class 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 must be avoided.
4. Java Stram API
The Java 8 Stream API can be used 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()));
Though it is a one-line expression, it has some problems too.
- It is not obvious and intuitive.
- It is still verbose.
- It involves the creation of unnecessary objects and extra computations
- This method can’t be used to create a Map.
To summarize the shortcomings, none of the above approaches treat the specific use case creating a small unmodifiable Collection as a first class problem.
Description and Usages of Factory Methods introduced in Java 9
To make it crisp and clear, static methods have been provided on List, Set, and Map interfaces which take the elements as arguments and return an instance of List, Set and Map respectively. The method is named of(…) for all the three interfaces.
1. List and Set
The signature and characteristics of List and Set factory methods are same:
static <E> List<E> of(E e1, E e2, E e3) static <E> Set<E> of(E e1, E e2, E e3)
usage of the methods:
List<String> list = List.of("a", "b", "c"); Set<String> set = Set.of("a", "b", "c");
As you can see, it’s very simple, short and concise.
In the example, we have used the method which takes exactly three elements as parameters and returns a List / Set of size 3. But, there are 12 overloaded versions of this method – eleven with 0 to 10 parameters and one with var-args:
static <E> List<E> of() static <E> List<E> of(E e1) static <E> List<E> of(E e1, E e2) // ....and so on static <E> List<E> of(E... elems)
For most practical purposes, 10 elements would be sufficient but if more are required, the var-args version can be used.
Now you may ask, what is the point of having 11 extra methods if there’s a var-args version which can work for any number of elements. The answer to that is performance. Every var-args method call implicitly creates an array. Having the overloaded methods avoid unnecessary object creation and thereof the garbage collection overhead.
During the creation of a Set using a factory method, if duplicate elements are passed as parameters, then IllegalArgumentException is thrown at runtime. For Example below line of code will throw an IllegalArgumentException .
public void IllegalArgumentExceptionText () { Set.of("a", "b", "b", "c"); }
An important point to note here is that since the factory methods use generics, primitive types are autoboxed.
If an array of primitive type is passed, a List of array of that primitive type is returned. For example:
int[] arr = { 1, 2, 3, 4, 5 }; List<int[]> list = List.of(arr);
In this case, a List<int[]> of size 1 is returned and the element at index 0 contains the array.
2. Map
Factory methods have also been added to Map interface. They work a little differently as Map has keys and values rather than a single type of element.
For up to 10 entries Map has overloaded constructors that take pairs of keys and values. For example we could build a map as follow:
Map<String, Integer> cities = Map.of("a", 1, "b", 2,"c",3);
The var-args case for Map is a little bit harder because in map, you need to have both keys and values, but in Java, methods can’t have two var-args parameters.
So the general case is handled by taking a var-args method of Map.Entry<K,V> objects and adding a static entry() method that constructs them. For example:
Map<String, Integer> cities = Map.ofEntries( entry("a", 1), entry("b", 2), entry("c", 3));
-
Passing in duplicate values for Key would throw an
IllegalArgumentException - The primitive type are also autoboxed in case of Map
Other Implementation Notes
1. Java 9 specific implementation of Collections
The collections created using the factory methods are not the most commonly used implementations.
For example, the List is not an ArrayList and the Map is not a HashMap. They are different implementations which are introduced in Java 9. These implementations are internal and their constructors are not made public.
2. Immutable
The collections created using factory methods are immutable and changing an element, adding new elements or removing an element throws UnsupportedOperationException.
3. No null Element Allowed
In the case of List and Set, no elements can be null. In the case of a Map, neither keys nor values can be null. Passing null argument throws a NullPointerException.
4 . Serialization
Collections created from factory methods are Serializable if the elements of the collection are Serializable
That's all for this topic. If you guys have any suggestions or queries, feel free to drop a comment. We would be happy to add that in our post. You can also contribute your articles by creating contributor account here.
Happy Learning 🙂
If you like the content on CodePumpkin and if you wish to do something for the community and the planet Earth, you can donate to our campaign for planting more trees at CodePumpkin Cauvery Calling Campaign.
We may not get time to plant a tree, but we can definitely donate ₹42 per Tree.
About the Author
Tags: ArrayList, Collection Framework, Collections, Factory Design Pattern, Java, Java9, List, Map, Set
Comments and Queries
If you want someone to read your code, please put the code inside <pre><code> and </code></pre> tags. For example:<pre><code class="java"> String foo = "bar"; </code></pre>For more information on supported HTML tags in disqus comment, click here.