Java 9’s Immutable Collections Are Easier To Create But Use With Caution

Code is easier to reason about when collections cannot be altered after their creation. Having to keep track of the current state of a collection as it gets passed around from this method to that equates to more mental balls to juggle. Mutating the state of methods` arguments is called a side effect and is a cardinal sin of functional programming. Since immutability is almost inarguably better why do Java developers generally completely ignore it?

 Instantiating an Immutable Collection in Java 8

Creating immutable collections in Java is convoluted to the point that it’s probably not worth the price of admission. This is a best practice implementation of an immutable set in Java 8:

Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("darth", "plagueis", "the", "wise")));

By comparison, here’s the same thing in Scala:

val set = Set("darth", "plagueis", "the", "wise")

The only way to create an immutable Map in Java in one statement is by using an instance initializer

Map<String, Integer> map = Collections.unmodifiableMap(
  new HashMap<String, Integer>() {{
    put("have", 1);
    put("the", 2);
    put("high", 3);
    put("ground", 4);
}});

which I’ve never written before and probably won’t ever again. Scala again reduced it down to a nice one-liner:

val map = Map("have" -> 1, "the" -> 2, "high" -> 3, "ground" -> 4)

Java 8 fixed many problems about dealing with collections through its Stream interface but continued the standard of verbose immutability:

Set<Integer> set = listOfStrings.stream()
  .map(String::hashCode)
  .collect(Collectors.collectingAndThen(Collectors.toSet(),
    Collections::unmodifiableSet));

 Instantiating an Immutable Collection in Java 9

Java 9 (general release 3/23/2017) fixes the problem of creating immutable collections for Set, List, and Map through its elegant of factory methods:

Set<String> immutableSet = Set.of("darth", "plagueis", "the", "wise");

Map<String, Integer> immutableMap = Map.of("have", 1, "the", 2, "high", 3, "ground", 4);

An interesting optimization the Java language team used was to include 12 overloaded implementations of of for each collection:

static <E> Set<E> of()  
static <E> Set<E> of(E e1)  
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)    
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5)  
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6)    
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, ..., E e7)   
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, ..., E e8)   
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, ..., E e9)   
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, ..., E e10)  
static <E> Set<E> of(E... elements) 

to avoid the performance ding of using varargs when less than 11 elements are used. There are several caveats about using these methods that developers need to be aware of: null and duplicates are not valid arguments as they are when directly adding to collections.

Set<String> set = new HashSet<>(Arrays.asList("a", "a")); // Ok
Set<String> set = Set.of("a", "a")); // IllegalArgumentException

Set<String> set = new HashSet<>();
set.add(null);  // Ok
Set<String> set = Set.of(null)); // NullPointerException

 Pitfalls of Java’s Immutable Collections

The danger of Java’s implementation is that because there is no interface specifically for immutable collections, those immutable Set and Map collections still have the mutable methods add/put and remove which will throw an UnsupportedOperationException if called. Scala created separate scala.collection and scala.collection.mutable interfaces for mutable and immutable collections to avoid this problem. Google’s popular Java Library Guava created separate public ImmutableSet, ImmutableMap, and ImmutableList classes.

Blankly looking at of, it isn’t obvious that the returned collection is immutable. A HashSet would be a reasonable guess since it is by far the most widely used Java set. Java’s comparable EnumSet.of(...) returns a mutable set. More than a few runtime exceptions are going to be thrown due to of‘s ambiguous return type.

 Conclusion

Of all the new features added to Java 9, the factory method of is one of the more useful in day-to-day programming but it needs to be used with caution. It may be worth specifically naming collections as immutable such as Set immutableSet = Set.of(...) to aid in maintainability.

 
288
Kudos
 
288
Kudos

Now read this

Create an Asynchronous Spring REST API with CompletableFutures and DeferredResult

Spring MVC is a powerful framework for building RESTful APIs. In accordance with the shift to asynchronous programming, Spring introduced the DeferredResult which an endpoint method can return so that its contents are propagated to the... Continue →

Subscribe to Carl Martensen

Don’t worry; we hate spam with a passion.
You can unsubscribe with one click.

F62tWw9EZzv86AwAMvUg