Guava's immutable collections
Guava is one of the most well-known Java libraries. It includes various interesting things, such as preconditions, caches, bloom filters and various collections.
Today, we’re going to discuss the immutable collections found in Guava.
Motivation Link to heading
Immutable objects are generally really good things to have in a program: being immutable means that they are automatically thread-safe (since no one can modify them, you don’t need locks!). Furthermore, since they cannot be change, they can be used as constants.
But, you might say, we already have the JDK! You can write things like:
List<String> letters = Collections.unmodifiableList(
Arrays.asList("a", "b", "c"));
It’s true, this works, although it’s a bit a mouthful to write. However, let’s assume you do this:
public class MyClass {
private final List<String> letters;
public MyClass(List<String> letters) {
this.letters = letters;
}
public List<String> getLetters() {
return Collections.unmodifableList(letters);
}
}
This code has two issues: the first is that, whenever you are calling the getter, you are creating a new object. That can be fixed quite simply though:
public class MyClass {
private final List<String> letters;
public MyClass(List<String> letters) {
this.letters = Collections.unmodifableList(letters);
}
public List<String> getLetters() {
return letters;
}
}
The second problem is more sublter and much more serious. The objects returned by the various Collections.unmodifiableXXX
methods are wrappers around the underlying collection. This means that, if the original collection changes, the supposedly unmodifable objects get modified!
Guava’s solution is to actually perform a copy of the input collection. Furthermore, Guava provides various constructors and factory methods to help you write more readable code, such as:
List<String> letters = ImmutableList.of("a", "b", "c");
List<String> letters = ImmutableList.copyOf(myStringList);
Variants Link to heading
Guava’s immutable collections includes quite a few variants: one for each JDK collection class, and one for each Guava collection. The most commonly used are:
- ImmutableCollection
- ImmutableList
- ImmutableSet
- ImmutableMap
- ImmutableMultiset
- ImmutableMultimap
- ImmutableBiMap
Other details Link to heading
Guava’simmutable collections do not allow null elements (and, for maps, null values nor null keys). While this prevents a few use cases, in general it’s quite useful to know that you cannot have null elements in a collection.
They also preserve insertion order: an ImmutableSet
can be iterated upon in the insertion order.
Finally, most collections have an useful asList
method, which creates a view of the data as a list, without doing any copy. This is mostly useful when working with ImmutableSet
.
Builders Link to heading
A really nice feature of Guava’s immutable collection is that they provide a Builder
to help construct them. An example:
ImmutableMap<String, String> languageExtension = ImmutableMap.<String, String>builder()
.put("C++", ".cpp")
.put("Java", ".java")
.put("Python", ".py")
.build();
Similar builders are provided ImmutableList
, ImmutableSet
and all other collections.
Returning immutable collections Link to heading
The general approach in Java is to declare fields, return methods or variables as raw interfaces, meaning you should declare your method to return a List<String>
rather than an ArrayList<String>
. However, there’s a case to be made for returning an ImmutableList<String>
rather than just a List<String>
: since ImmutableList
extends List
, client code isn’t forced to “think in Guava”. On the other hand, returning an ImmutableList
makes it very clear to the client that the result cannot be modified.
In short, it can help keeping the code clear and express your intentions clearly.
Conclusions Link to heading
Guava’s immutable collections are a really useful part of an amazing library. Go and use them!