Who did it better? Guava or Java?

Who did it better? Guava or Java?

ImmutableCollection vs Collections.unmodifiableCollection and more

Guava is one of if not the most popular Java libraries. But it has been around for a long time and Java has since implemented many features comparable to those found in Guava. The question is now, which one did it better?

Title photo by Juan Camilo Guarin P on Unsplash

Immutable Collections

Guava has immutable implementations for all of Java’s common collections and of course its own collections. While Java does not have interfaces for it, it offers two methods to create immutable collections: [Collections.unmodifiableXXX](docs.oracle.com/javase/9/docs/api/java/util..) to wrap existing collections and [XXX.of](docs.oracle.com/javase/9/docs/api/java/util..) to create new ones.

I’ll take the example of ImmutableList, Collections.unmodifiableList and List.of for this post to simplify it, but the same things apply to all other versions.

An additional dependency

Using Guava will introduce a new dependency to your code. In my opinion it’s well worth it. Even if you choose to go with Java’s implementations for the examples here, Guava still offers many utilities that make it worthwhile.

A different story is including Guava classes in your public API. In that case you’d force every user of that API to use Guava and your specific version of it. You’re also locked out from removing it as a dependency.

This might be fine for libraries where you know that the users will also be using Guava, but even there it should generally be avoided.

An explicit tell

When you see ImmutableList somewhere, you know that that value is immutable. Not so much with Java’s methods, which will simply return a List.

This explicit tell is very useful:

  • In parameters: The caller is guaranteed that the method won’t mutate the list. On the other hand, the user must first wrap their collection in an immutable one.

  • In return values: The caller knows that this collection shouldn’t be mutated. It also helps the implementation since you can often avoid to create new instances and only update the list when needed. And you don’t have to look at the documentation of a method to figure out whether mutating the returned collection is fine.

  • In fields and variables: When reading the code, it is immediately apparent that this field or variable isn’t and shouldn’t be mutated. This can help with some of the mental overhead. It also catches some potential errors at “linting-time”.

Less defensive copies

In order to better guarantee that a given list is not changed in a given context, it is common to create defensive copies. “Defensive copies” refers to the practice of first creating an immutable version of a given collection and then operating strictly on that one.

This practice can help ensure that a list isn’t mutated when it shouldn’t be. However, this will only be noticed at runtime and costs some runtime compared to using the list directly.

Using the immutable version directly as parameter might force the user to create an immutable version instead, but it also signals to them that it can be reused in other methods that also don’t mutate the list. This might result in less copies being generated, but it will definitely result in more clarity, since I don’t need to see the implementation in order to know whether the list will be mutated.

ImmutableList is truly immutable

Java’s Collections.unmodifiableList will only provide an immutable view on a collection. This is an important distinction.

First an foremost, this means that they aren’t truly immutable: They will change if the original instance is changed.

It also means that they won’t be able to take advantage of the immutability as much as Guava’s implementations can. They must still include unncessary concurrency checks, might allocate unnecessary space and more.

These differences are somewhat less relevant for the List.of method, which will create a new list anyways — even if the overload is used that takes an array of strings. However, it’s important to note that I couldn’t find this mentioned in any official documentation, so, technically, there is no guarantee that this will stay this way or is the same in all implementations.

Neither ImmutableList nor Java’s unmodifiable list prevent their elements from being modified, though. For that you’d have to create a deep copy. During my research, I stumbled upon this nice blog post outlining the differences in those approaches in a nice, visual way.

ImmutableList is a List

This is maybe less of an argument for Guava’s implementation and more so an important note: ImmutableList implements the List interface.

This means that you can use an instance of ImmutableList everywhere that you could use an unmodifiable list from Java.

There is one case to watch out for, though: When the called method creates a defensive copy using the Java methods. In that case it will create a new instance instead of reusing the old one, when you pass an ImmutableList. When provided Java’s unmodifiable list, it would instead simply use that instance directly.

The same is true in reverse: ImmutableList.copyOf will not create a new instance if a ImmutableList is passed to it, but will if an unmodifiable list is passed instead.

Implementing the List interface also means that they share one of the big problems: You can still call methods that would modify a collection. It will then only fail at runtime, not compile time. This is mitigated better by Guava’s version since they actively mark those methods as deprecated, which will show up in most editors. Of course this only works when using the raw ImmutableList and not when using it as List.

Verdict

I believe that Guava’s collections bring a lot of value compared to the Java standard library methods, so I would always use those if Guava is in the project. I might even consider adding Guava just for them, but luckily Guava has plenty of other good features that make justifying adding it pretty easy.

Optional

Before Java added Optional, Guava did. In this case, the probably best comparison is the one found in Guava’s own documentation:

A new Optional class was added for Java 8. The two classes are extremely similar, but incompatible (they cannot share a common supertype). All known differences are listed either here or with the relevant methods below.

  • This class is serializable; java.util.Optional is not.
  • java.util.Optional has the additional methods ifPresent, filter, flatMap, and orElseThrow.
  • java.util offers the primitive-specialized versions OptionalInt, OptionalLong and OptionalDouble, the use of which is recommended; Guava does not have these. There are no plans to deprecate this class in the foreseeable future. However, we do gently recommend that you prefer the new, standard Java class whenever possible.

So by Google’s own recommendation, you should opt for the Java library version. I tend to agree, mainly because you can use that version safely in public APIs. The extra methods (mainly filter and flatMap) are a nice bonus, too.

Weirdly enough, for some reason, those two methods together with map are missing from their primitive counterparts. I guess Oracle didn’t think them useful enough.

Charset vs StandardCharset

These two are virtually the same. Both provide the same list of charsets as static fields: UTF-8, UTF-16, UTF-16BE, UTF-16LE, US-ASCII and ISO-8859–1.

It also helps that Guava’s documentation has this sentence for each of the constants:

Note for Java 7 and later: this constant should be treated as deprecated; use StandardCharsets.XXX instead.

So, for the same reason as with Optional, I’d recommend using Java’s versions instead.

Summary

In summary, I’d go with Guava’s version of immutable collections, but with the standard library versions of Optional and charsets. For the later two, there is just not enough reason to use a library over the existing one.

To recap, the reasons for the immutable collections are:

  • An explicit tell that something is immutable.

  • They can be used as a simple List just the same as Java’s implementation.

  • They aren’t modified if the source collection is modified (if one exists).

  • They can use the immutability to their advantage when implementing the collections. This means sometimes better runtime of methods and less required memory.

However, they also have some negatives:

  • They require an extra dependency, which is especially problematic if you want to use the classes/methods in public APIs.

  • They still have the same problems that come from implementing List.

One last note: Guava also has its own ways to work with futures, while Java now has CompletableFuture. This topic deserves its own blog post, however, so I have written one here.