Java 9: Create Lists, Maps and Sets easier than ever using the new factory methods for immutable collections

Working with collections in Java has always been a bit of a hassle — especially if you want to initialise the collection and add data to it in one go.

Where other languages got handy one-liners, the java coder had a way more cumbersome job ahead by first having to initialise the collection, then add the data one by one.

Luckily this has been taken care of in Java 9 by introducing a bunch of new collection factories with the goal of making this type of work easier than ever.

Creating a List with values using List.of

In the pre-Java 9 era you had a few options.

You could initialise an implementing class of the List interface — e.g. ArrayList — then start adding elements to it one by one in a mutable fashion.

You also could use the less intuitive, yet way more concise, Arrays.toList.

However, with the release of Java 9, we finally got a concise and easy to understand way of creating lists with values using the static method List.of.

List.of("Luke", "Eva", "John");  

Easy right? All we have to do is to add the elements we want as parameters.

Creating a Map with values using Map.of

Looking at Maps we see the same improvement.

Instead of initialising an implementation of the Map interface before adding values one by one, we now got a static method in the Map interface — Map.of.

Map.of("name", "Luke", "age", "23", "city", "New York");  

The way this method works is that you provide one key then the associated value, then the next key then the value, and so on.

Creating a Set with values using Set.of

Set interface brings the same static method —
Set.of.

Set.of("Luke", "Eva", "John");  

How do the collections behave?

Great, we've seen that these new factory methods bring a concise and easy to understand syntax.

But how do they work under the hood? What kind of behavior can we expect?

Immutable

First of all, all of these implementations are immutable, which means that they cannot be modified.

So what happens if you use modifying methods like add, remove, put, replace etc? They'll throw UnsupportedOperationException.

List.of(1).add(2);  
// > java.lang.UnsupportedOperationException

Set.of(1).add(2);  
// > java.lang.UnsupportedOperationException

Map.of("name", "Luke").put("age", "20");  
// > java.lang.UnsupportedOperationException

What's worth mentioning is that even if the collections are immutable doesn't mean that the elements they contains are.

Say we got a list of articles and we output their name.

List<Article> articles = List.of(article1, article2);

articles.forEach(a -> System.out.println(a.getTitle()));

// > "Title 1"
//   "Title 2"

Now let's say that these objects expose a setter method that allows us to change their title — setTitle.

Now we're actually able to modify the values of the immutable collection.

articles.get(0).setTitle("Modified title");

articles.forEach(a -> System.out.println(a.getTitle()));

// > "Modified title"
//   "Title 2"

Same goes for Maps and Sets — it's possible to change mutable object within immutable collections.

So you still have to be careful with what you expose in the objects themselves.

Not allowing null values

Another great thing about these collections is that they don't accept null values.

Trying to add a null value to one of them will result in a NullPointerException.

// Using null in a List
List.of(1, null);  
// > java.lang.NullPointerException

// Using null in a Set
Set.of(1, null);  
// > java.lang.NullPointerException

// Using null as a value in a Map
Map.of("name", null);  
// > java.lang.NullPointerException

// Using null as a key in a Map
Map.of(null, "Luke");  
// > java.lang.NullPointerException

The collections are value based.

An important thing about these factory methods is that they are value based. This means that the factories could create new instances as well as reuse existing ones.

What this means is that you can't trust the identity of the returned instance. You need to test equality based on the values and not the reference.

Not allowing duplicate keys

If you would use put in a a traditional sense, new entries using the same key would simply overwrite the old value.

Map.of will handle this slightly different.

Duplicate keys won't override each other, they'll result in an exception, because let's face it — we're probably doing something wrong if we set the same key twice during the initialisation.

Map.of("name", "Luke","name", "Eva");  
// > java.lang.IllegalArgumentException: duplicate key: name

Conclusion

The new factory methods introduced on List, Set and Map is another great step in cleaning up Java making it more elegant with less boiler plate code. Not only do we get an improved syntax, but it's also embracing great principles like immutability.

For more information, take a look at the Java Documentation for List.of, Map.of and Set.of.