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?
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));
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
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.
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.