Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Convenience Factory Methods for Collections in Java 9

DZone's Guide to

Convenience Factory Methods for Collections in Java 9

Java 9 brings with it some new convenience methods for dealing with collections. Here, we look at the role they play in Lists, Sets, and Maps.

· Java Zone ·
Free Resource

Learn how to build stream processing applications in Java-includes reference application. Brought to you in partnership with Hazelcast.

Java 9 doesn't bring as many dramatic changes to our way of coding as its predecessor did, but surely we will have some fancy features — one of which I would like to present to you in this article. When I started coding in Groovy about 5 years ago, I was amazed by the collections support there — especially when it came to initializing an immutable list. I could do it in one simple line:

def band = ["Bruce", "Steve", "Adrian", "Dave", "Janic", "Nicko"].asImmutable()


This year, I also started coding in Scala, and here we have some fancy tricks like:

val band = "Bruce" :: "Steve" :: "Adrian" :: "Dave" :: "Janick" :: "Nicko" :: Nil

or simply

val band = List("Bruce", "Steve", "Adrian", "Dave", "Janick", "Nicko")


At the same time, Java seemed to be torturing me with an add statement list:

List<String> band = new ArrayList<>();
band.add("Bruce");
band.add("Steve");
band.add("Adrian");
band.add("Janick");
band.add("Nicko");
band = Collections.unmodifiableList(band);


Or the so-called "double-brace" initialization (which in fact isn't any special Java feature, but rather a workaround benefiting from anonymous classes and initialization blocks):

List<String> band = Collections.unmodifiableList(new ArrayList<>() {
    {
        add("Bruce");
        add("Steve");
        add("Adrian");
        add("Janick");
        add("Nicko");
    }
});


Or using the  Arrays API to convert an array to an  ArrayList:

List<String> band = Collections
  .unmodifiableList(Arrays.asList("Bruce","Steve","Adrian", 
                                  "Dave", "Janick","Nicko"));


Lately, I could also benefit from the Stream API:

List<String> band = Collections
  .unmodifiableList(Stream.of("Bruce","Steve","Adrian", "Dave", "Janick","Nicko")
    .collect(toList()));


The last two options are the cutest ones, but why do I need to first create an array or a stream in order to create a list, and why can't I just use the  Collections API instead of the  Arrays API or the  Stream API? Well, I don't even want to recall creating Sets or Maps in Java — thinking of it makes me wake up in a cold sweat in the middle of the night. Fortunately, the authors of Java 9 implemented  JEP 269: Convenience Factory Methods for Collections, which simply provides a set of static factory methods supporting the creation of immutable collection instances. Saviors! Read on to get the overview of this feature.

Immutable Collections

Before we dig into the topic, you should know what immutable collections really are. They are collections that cannot be modified once they are created. What I mean by "modified" is that their state (references they hold, the order in which those references are being kept, and the number of elements) will stay untouched. Please note that if an immutable collection holds mutable objects, their state won't be protected anyhow. Although immutable collections still implement ListSet, or Map interfaces, methods modifying their contents throw an UnsupportedOperationException. You will find sets of such methods in subsequent sections.

Implementation

Implementation can be divided into two main parts. Firstly, the java.util package was enriched with the package-private ImmutableCollections class, which contains classes providing the immutability feature. Secondly, instances of those classes are being created with the help of static factory methods in already-existing interfaces, i.e. ListSet, and Map. In the following sections, you will find descriptions of both sets of functionalities per each collection interface.

List

An immutable list has an abstract base class  AbstractImmutableList<E> and four implementations:

  •  List0<E>  
  •  List1<E>  
  •  List2<E>  
  •  ListN<E>  

Each of these types corresponds to the number of elements that are used to their creation. In the java.util.List interface, we have 12 static factory methods that use the above implementations to create immutable objects:

// creates empty immutable list
static <E> List<E> of()

// creates one-element immutable list
static <E> List<E> of(E e1)

// creates two-element immutable list
static <E> List<E> of(E e1, E e2)

...

// creates ten-element immutable list
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)

// creates N-element immutable list
static <E> List<E> of(E... elements)


Methods that throw UnsupportedOperationException:

boolean add(E e);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
void    clear();
boolean remove(Object o);
boolean removeAll(Collection<?> c);
boolean removeIf(Predicate<? super E> filter);
void    replaceAll(UnaryOperator<E> operator);
boolean retainAll(Collection<?> c);
void    sort(Comparator<? super E> c);


Apart from protecting the content of our list, we also get a validation that prevents us from initiating a list with a null value. Trying to run the following piece of code will end up with a NullPointerException:

// throws NullPointerException
List<String> band = List.of("Bruce","Steve","Adrian", "Dave", "Janick", null);


Now, here is an example of how to properly create an immutable list:

List<String> band = List.of("Bruce","Steve","Adrian", "Dave", "Janick","Nicko");


Set

An immutable set is implemented similarly to how we saw with the List interface. It has an abstract base class  AbstractImmutableSet<E> and four implementations:

  •  Set0<E>  
  •  Set1<E>  
  •  Set2<E>  
  •  SetN<E>  

That again correspond to the number of elements that are used to their creation. In the  java.util.Set interface, we have 12 static factory methods:

// creates empty immutable set
static <E> Set<E> of()

// creates one-element immutable set
static <E> Set<E> of(E e1)

// creates two-element immutable set
static <E> Set<E> of(E e1, E e2)

...

// creates ten-element immutable set
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)

// creates N-element immutable set
static <E> Set<E> of(E... elements)


Methods that throw UnsupportedOperationException :

boolean add(E e)
boolean addAll(Collection<? extends E> c)
void    clear()
boolean remove(Object o)
boolean removeAll(Collection<?> c)
boolean removeIf(Predicate<? super E> filter)
boolean retainAll(Collection<?> c)


Like with immutable lists, we cannot instantiate a Set with a null value:

// throws NullPointerException
Set<String> band = Set.of("Bruce","Steve","Adrian", "Dave", "Janick", null);


You should also know that sets differ from lists in a way — they cannot have duplicate values. With the newly provided factory methods, we won't be able to initialize an immutable Set passing more than one object of the same value — we will get IllegalArgumentException:

// throws IllegalArgumentException
Set<String> guitarists = Set.of("Adrian", "Dave", "Janick", "Janick");


Now, here is an example of how to properly create an immutable set:

Set<String> band = Set.of("Bruce","Steve","Adrian", "Dave", "Janick","Nicko");


Map

Before we describe the technical details of immutable maps, we should start with the concept of an entry (java.util.Map.Entry interface) — an aggregate of a key-value pair. From Java 9, we have yet another entry's package private implementation —  java.util.KeyValueHolder — an immutable container that prevents instantiating an entry with a key or value equal to null (throwing a NullPointerException if done so).

In order to create an immutable entry, we can use the following static factory method from the  java.util.Map interface:

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


An immutable map has an abstract base class,  AbstractImmutableMap<K, V>  , with three implementations:

  •  Map0<K, V>
  •  Map1<K, V>
  •  MapN<K, V>  

Again we have the following set of factory methods inside the  java.util.Map interface:

// creates an empty map
static <K, V> Map<K, V> of()

// creates one-element map
static <K, V> Map<K, V> of(K k1, V v1)

// creates two-element map
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2)

...

// creates ten-element map
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)

// creates N-element map
static <K, V> Map<K, V> ofEntries(Entry<? extends K, ? extends V>... entries)


You can see that it differs from List or Set factory methods from the previous sections. Using one of the of methods, we can create immutable maps that contain up to 10 elements. If we want to have a bigger one, we need to use the ofEntries method, accepting varargs of Entity. It shouldn't be any surprise, as we can use varargs for only one argument in a method, so we have no way of passing keys and values of different types this way.

Like with lists and sets, we have some methods that throw an UnsupportedOperationException:

void clear()
V compute(K key, BiFunction<? super K,? super V,? extends V> rf)
V computeIfAbsent(K key, Function<? super K,? extends V> mf)
V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> rf)
V merge(K key, V value, BiFunction<? super V,? super V,? extends V> rf)
V put(K key, V value)
void putAll(Map<? extends K,? extends V> m)
V putIfAbsent(K key, V value)
V remove(Object key)
boolean remove(Object key, Object value)
V replace(K key, V value)
boolean replace(K key, V oldValue, V newValue)
void replaceAll(BiFunction<? super K,? super V,? extends V> f)


Regardless the way we create immutable maps, we won't be able to instantiate it with a key, value, or whole entry equal to null. Below are the examples of code that will throw NullPointerExceptions:

// throws NullPointerExcepton because of null key
Map<String, Long> age = Map.of(null, 59L, "Steve", 61L);
// throws NullPointerExcepton because of null value
Map<String, Long> age = Map.of("Bruce", null, "Steve", 61L);
// throws NullPointerExcepton because of null entry
Map<String, Long> age = Map.ofEntries(Map.entry("Bruce", 59L), null);


Similarly to immutable sets, we cannot create a map with duplicate values. Trying to do so will end up throwing IllegalArgumentException:

Map<String, Long> age = Map.of("Bruce", 59L, "Bruce", 59L);
Map<String, Long> age = Map.ofEntries(Map.entry("Bruce", 59L),
                                      Map.entry("Bruce", 59L));


And here are some examples of how can we properly create immutable maps in Java 9:

Map<String, Long> age = Map.of("Bruce", 59L, "Steve", 61L, "Dave", 60L,
                               "Adrian", 60L, "Janick", 60L, "Nicko", 65L);
Map<String, Long> age = Map.ofEntries(Map.entry("Bruce", 59L),
                                      Map.entry("Steve", 61L),
                                      Map.entry("Dave", 60L),
                                      Map.entry("Adrian", 60L),
                                      Map.entry("Janick", 60L),
                                      Map.entry("Nicko", 65L));


Conclusions

Since Java 9's arrival, creating immutable collections has been very convenient. We have a set of static factory methods for each interface that, apart from creating immutable objects, prevent us from inserting nulls or duplicates (in Set and Map). We are warned about any problems at creation time, and we are safe from getting NullPointerExceptions while traversing collection elements. It is a tiny feature, but surely a useful one!

Learn how to build distributed stream processing applications in Java that elastically scale to meet demand- includes reference application.  Brought to you in partnership with Hazelcast.

Topics:
java ,java 9 ,collections ,convenience factory methods ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}