Code Pumpkin

Convenience Factory Methods for Collections | Java9

April 19, 2017
Posted by Pumpkin
Subscribe

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 ListSet, and Map interfaces which take the elements as arguments and return an instance of ListSet 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 it for Convenience Factory Methods of Collections.  Click Here to read about other Java 9 Features.

Happy Learning smiley

Tags: , , , , , , , ,


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.

Total Post : 80
Subscribe
Contribute Your Articles

Interview Experiences

Related Books

Like Us On Facebook

Alexa Page Rank