Immutable Collections in Google Guava

Introduction

I earlier have written a post on the Convenience Factory Methods for Collections. It talks about the static factory methods added to Java Collections (List, Set and Map) in Java 9. It enables us to create immutable collections and map instances. If you are using Java version lesser than 9, then you cannot use those methods. Google Guava library provides ways to create Immutable Collections. In this post, we will learn about the Immutable Collections in Google Guava.

Setting up Google Guava

For Maven projects, you can import Google Guava into your project by adding the below snippet in your pom.xml.

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1-jre</version>
</dependency>

For gradle,

implementation 'com.google.guava:guava:30.1-jre'

Note that 30.1-jre is the latest version as of writing this. You can find the current latest version from the Google Guava’s Maven artifact page.

Immutable Collections from Google Guava

We can create immutable collections using two ways through Google Guava. 

  • The of() method
  • The Builder

Here, the of() method is similar to the Java 9’s factory method in Collections. 

There is also a copyOf() method from which you can create an Immutable collection. But that requires you to have an existing collection or an Iterator/Iterable. I will focus on the of() method and the builder methods offered by Google Guava library to create immutable collections in this post.

Creating an Immutable List using Google Guava

ImmutableList#of

The of() is a static factory method in the ImmutableList class in Google Guava which helps to build an immutable list. There are 12 overloaded of() methods.

  • One taking zero arguments – It returns an empty immutable list.
  • Overloaded of() methods accepting from one value to eleven values.
List<Integer> emptyList = ImmutableList.of();

List<Integer> list = ImmutableList.of(1);
System.out.println(list); //[1]

list = ImmutableList.of(1, 2, 3);
System.out.println(list); //[1, 2, 3]

list = ImmutableList.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
System.out.println(list); //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

Beyond 11 elements, there is another ImmutableList.of() method which has a var-args as the last argument.

List<Integer> list = ImmutableList.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
System.out.println(list); //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

Hence to us as the users, we don’t have to worry about which method is being called. We can just pass the individual elements and have them converted into a list which is immutable.

Since the returned list is immutable, any attempt to add or remove elements (modify the list structurally) from the returned list will throw a java.lang.UnsupportedOperationException.

ImmutableList.builder()

The Google Guava ImmutableList also offers a builder to build the list. There are two ways to create the builder.

  1. By calling the Builder constructor
  2. By calling the builder static method

Both are equivalent.

ImmutableList.Builder<Integer> builder = new ImmutableList.Builder<>();
or
ImmutableList.Builder<Integer> builder = ImmutableList.builder();

Once we have created a builder instance, we can add elements to the builder by calling the add method.

The add method returns this (the current instance) back and hence we can chain the calls as shown below.

builder.add(1)
       .add(2)
       .add(3);

Once we have added the individual elements, we can create the ImmutableList by calling the build() method.

System.out.println(builder.build()); //[1, 2, 3]

We don’t have to have a local reference to the builder instance. Instead, we can chain the add method calls to the call which creates the builder.

ImmutableList.<Integer>builder()
        .add(1)
        .add(2)
        .add(3)
        .build()

Passing multiple values to ImmutableList#builder

The ImmutableList’s builder also provides an add method to which we can pass a var-args. Hence we can add multiple values in one call like:

System.out.println(ImmutableList.<Integer>builder()
        .add(1, 2, 3)
        .build()); //[1, 2, 3]

It also provides an addAll method to which we can pass an Iterator or an Iterable.

List<Integer> firstList = ImmutableList.of(1, 2, 3);
List<Integer> finalList = ImmutableList.<Integer>builder()
                .addAll(firstList)
                .add(4, 5, 6)
                .build();
System.out.println(finalList); //[1, 2, 3, 4, 5]

Creating an Immutable Set using Google Guava

Creating an Immutable Set using the ImmutableSet class is very similar to how we used an ImmutableList.

ImmutableSet#of

Similar to the ImmutableList#of method, ImmutableSet too has overloaded methods which help in creating an immutable set from the elements we pass. But it has methods accepting only up to 5 distinct elements. Hence, if we pass more than 5 elements, it will call the overloaded of() method accepting a var-args parameter.

Set<Integer> emptySet = ImmutableSet.of();

Set<Integer> set = ImmutableSet.of(1);
System.out.println(set); //[1]

set = ImmutableSet.of(1, 2, 3);
System.out.println(set); //[1, 2, 3]

set = ImmutableSet.of(1, 2, 3, 4, 5);
System.out.println(set); //[1, 2, 3, 4, 5]

set = ImmutableSet.of(1, 2, 3, 4, 5, 6);
System.out.println(set); //[1, 2, 3, 4, 5, 6]

Since it is a set, if we pass duplicate elements, the result set will not have the duplicates.

Set<Integer> set = ImmutableSet.of(1, 1, 2, 3, 2);
System.out.println(set); //[1, 2, 3]

ImmutableSet#builder

ImmutableSet offers builder to create an ImmutableSet in a declarative way.

ImmutableSet.Builder<Integer> builder = ImmutableSet.builder();
builder.add(1)
        .add(2)
        .add(3);
System.out.println(builder.build()); //[1, 2, 3]

As seen earlier, we can chain the calls to create a builder, add and build together as shown below:

System.out.println(ImmutableSet.<Integer>builder()
        .add(1)
        .add(2)
        .add(3)
        .build()); //[1, 2, 3]
        
System.out.println(ImmutableSet.<Integer>builder()
        .add(1, 2, 3)
        .build()); //[1, 2, 3]

ImmutableSet#builder addAll

The addAll method allows us to pass in an Iterable or an Iterator.

Set<Integer> firstSet = ImmutableSet.of(1, 2, 3);
Set<Integer> finalSet = ImmutableSet.<Integer>builder()
        .addAll(firstSet)
        .add(1, 4, 5)
        .build();
System.out.println(finalSet); //[1, 2, 3, 4, 5]

Creating an Immutable Map using Google Guava

ImmutableMap#of

The ImmutableMap class has overloaded of() methods which accept up to five key-value pairs. The of() method accepts the key and values alternatively. 

Let us create a map with string as the key and integer as the value.

Map<String, Integer> emptyMap = ImmutableMap.of();

Map<String, Integer> map = ImmutableMap.of("a", 1);
System.out.println(map); //{a=1}

map = ImmutableMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5);
System.out.println(map); //{a=1, b=2, c=3, d=4, e=5}

If we want to pass more than 5 key-value pairs, we have to use a builder (discussed in the next section) as var-args cannot be used because the types of keys and values can be different.

ImmutableMap#builder

Once we create a builder from the ImmutableMap, we call the put method to add the key-value pairs into the builder. After adding all entries, we can create the immutable map by calling the build method.

Map<String, Integer> map = ImmutableMap.<String, Integer>builder()
        .put("a", 1)
        .put("b", 2)
        .put("c", 3)
        .build();
System.out.println(map); //{a=1, b=2, c=3}

The put method does not allow us to pass duplicate keys. It will fail with an java.lang.IllegalArgumentException when we pass the same key more than once.

ImmutableMap.<String, Integer>builder()
        .put("a", 1)
        .put("b", 2)
        .put("c", 3)
        .put("c", 44)
        .build();

The above code snipped will fail with an exception:

java.lang.IllegalArgumentException: Multiple entries with same key: c=44 and c=3

ImmutableMap#builder putAll

If we want to add all the entries from an existing map into the builder, we can use the putAll method.

Map<String, Integer> map1 = ImmutableMap.<String, Integer>builder()
        .put("a", 1)
        .put("b", 2)
        .put("c", 3)
        .build();
Map<String, Integer> map2 = ImmutableMap.<String, Integer>builder()
        .putAll(map1)
        .put("d", 4)
        .build();
System.out.println(map2); //{a=1, b=2, c=3, d=4}

Again, we cannot add the keys already added to the builder. In the above example, we cannot add keys a, b and c again when building the second map.

Conclusion

In this post we learnt about the Immutable Collections in Google Guava. Specifically, we learnt about the static methods of and the builder methods from the ImmutableList, ImmutableSet, and ImmutableMap classes. I also recommend checking out the other features of Google Guava.

Leave a Reply