Introduction to Google Collections


Did you ever felt that working with the Java Collections Framework could be more elegant or efficient? Then you really should consider to use the Google Collections API. It's a great utility library every Java developer should know. Take the time to read this introduction to easily getting started with Google Collections.

The Google Collections Library 1.0 is a set of new collection types, implementations and related goodness for Java 5 and higher, brought to you by Google. It is a natural extension of the Java Collections Framework you already know and love.

Working with Lists

First let me show you some convenient List utilities. The class Lists contains plenty of static utility methods for building and manipulating lists (analog Sets and Maps for set and map utilities). Let's take a look at the following example source code:

List<String> list1 = Lists.newArrayList("1", "2", "3");
List<Double> list2 = Lists.transform(list1, new Function<String, Double>() {
   public Double apply(String from) {
      return Double.parseDouble(from);
   }
});

System.out.println(Joiner.on(" | ").join(list2));

The code is self-explanatory. I'm using some factory method to create an array list. Then this list will be transformed to another list by applying some generic function to all lists elements. The transformed list will be printed to the console by using a Joiner which let you easily build strings from collections. The result looks like this:

1.0 | 2.0 | 3.0

Extensions to Iterators and Iterables

Similiar to Lists, Sets and Maps Google Collections serves convient utilities for iterating over collections of elements. The classes Iterators and Iterables contain various helpful static methods for manipulating, combining, filtering or transforming iterable collections. To cut a long story short check out this code snippet:

List<String> list = Lists.newArrayList("A100", "B100", null, "B200");
Iterable<String> filtered = Iterables.filter(list, new Predicate<String>() {
   public boolean apply(String input) {
      return input == null || input.startsWith("B");
   }
});

System.out.println(Joiner.on("; ").useForNull("B000").join(filtered));

First a list will be constructed containing some strings and a null value. Then this list will be filtered, we only want all the strings starting with B and the null value. Finally the result will be printed to the console replacing all null values with B000. Executing the code results in:

B100; B000; B200

Building Predicate Logic

Google Collections makes it easy to work with logical predicates. The class Predicates contains appropriate static methods such as and, or, not or in to build complex predicates. As you can see in the following example these predicates are clearly represented in combination with static imports (a Java 5 feature). It's also possible to combine predicates with functions as you can see in the second example.

import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.compose;
import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;

List<String> list1 = Lists.newArrayList("1", "2", "3");
List<String> list2 = Lists.newArrayList("1", "4", "5");
List<String> list3 = Lists.newArrayList("1", "4", "6");

boolean result = and( not( in(list1) ), in(list2), in(list3)).apply("1");

System.out.println(result);  // false

List<String> list1 = Lists.newArrayList("A1", "A2", "A3");
boolean result = compose(in(list1), new Function<String, String>() {
   public String apply(String from) {
      return "A" + from;
   }
}).apply("1");

System.out.println(result);  // true

Combining and Modifying Comparators

One thing I really like about Google Collections is the class Ordering which let you easily combine multiple Comparators to perform flexible comparisons on runtime. Think of a class Person with different members such as first and last name. We want to be able to order persons by multiple members without implementing verbose comparisons. This can be easily achived with Google Collections.

public class Person {
   private String firstName;
   private String lastName;

   public Person(String firstName, String lastName) {
      this.setFirstName(firstName);
      this.setLastName(lastName);
   }

   @Override
   public String toString() {
      return getFirstName() + " " + getLastName();
   }

   public void setFirstName(String firstName) {
      this.firstName = firstName;
   }

   public String getFirstName() {
      return firstName;
   }

   public void setLastName(String lastName) {
      this.lastName = lastName;
   }

   public String getLastName() {
      return lastName;
   }
}

First we define two simple Comparators for each member involved by the ordering. Then we can build different orderings with ease using the comparators in combination with static methods from the class Ordering.

List<Person> persons = Lists.newArrayList(
   new Person("Alfred", "Hitchcock"),
   null,
   new Person("Homer", "Simpson"),
   new Person("Peter", "Fox"),
   new Person("Bart", "Simpson"));

Comparator<Person> lastNameComparator = new Comparator<Person>() {
   public int compare(Person p1, Person p2) {
      return p1.getLastName().compareTo(p2.getLastName());
   }
};

Comparator<Person> firstNameComparator = new Comparator<Person>() {
   public int compare(Person p1, Person p2) {
      return p1.getFirstName().compareTo(p2.getFirstName());
   }
};

// order by last name ascending
Ordering<Person> ordering = Ordering.from(lastNameComparator);
System.out.println(ordering.nullsLast().sortedCopy(persons));

// order by last name descending, first name ascending
ordering = ordering.reverse().compound(firstNameComparator);
System.out.println(ordering.nullsLast().sortedCopy(persons));

As you can see its easy to combine the comparators to complex orderings. Besides you don't have to bother with null values. Executing the code sample results in:

[Peter Fox, Alfred Hitchcock, Homer Simpson, Bart Simpson, null]
[Bart Simpson, Homer Simpson, Alfred Hitchcock, Peter Fox, null]

Working with Maps

Google Collections comprises very nice Map support. Not only does the library provide convient utility methods via the class Maps. Also it serves own map implementations like BiMap which preserves uniqueness not only of its keys but also of its values.

BiMap<Integer,String> biMap = HashBiMap.create();
biMap.put(Integer.valueOf(5), "Five");
biMap.put(Integer.valueOf(1), "One");
biMap.put(Integer.valueOf(9), "Nine");
biMap.put(Integer.valueOf(5), "Another Five");
biMap.put(Integer.valueOf(55), "Five");

System.out.println(biMap);
System.out.println(biMap.inverse());

This example shows the functionality of Bimaps. Putting equal keys or values results in overriding the appropriate entries. The result looks like this:

{9=Nine, 55=Five, 1=One, 5=Another Five}
{Nine=9, Another Five=5, Five=55, One=1}

Google Collections enables you to easily build immutable maps via builder:

ImmutableMap<String,Integer> map1 =
   new ImmutableMap.Builder<String,Integer>()
      .put("one", 1)
      .put("two", 2)
      .put("three", 3)
      .build();

ImmutableMap<String,Integer> map2 =
   new ImmutableMap.Builder<String,Integer>()
      .put("five", 5)
      .put("four", 4)
      .put("three", 3)
      .build();

MapDifference<String, Integer> difference = Maps.difference(map1, map2);
System.out.println(difference.entriesInCommon());
System.out.println(difference.entriesOnlyOnLeft());
System.out.println(difference.entriesOnlyOnRight());

As you can see computing the difference between two maps is quite comfortable using the utility class Maps. Here is the result of this snippet:

{three=3}
{one=1, two=2}
{five=5, four=4}

Also it's easy to filter a map by some predicate:

ImmutableMap<Integer,String> map =
   new ImmutableMap.Builder<Integer,String>()
      .put(10, "Ten")
      .put(20, "Twenty")
      .put(30, "Thirty")
      .build();

Map<Integer,String> filtered = Maps.filterKeys(map, Predicates.or(Predicates.equalTo(10), Predicates.equalTo(30)));
System.out.println(filtered);

The result looks like this:

{10=Ten, 30=Thirty}

Finally, let's apply some transformations to the values of a map:

ImmutableMap<Integer,String> map =
   new ImmutableMap.Builder<Integer,String>()
      .put(10, "10")
      .put(20, "20")
      .put(30, "30")
      .build();

Map<Integer,String> transformed = Maps.transformValues(map, new Function<String,String>() {
   public String apply(String from) {
      return "X" + from;
   }
});

System.out.println(transformed);

Result:

{10=X10, 20=X20, 30=X30}

This was a short introduction to Google Collection. This article demonstrated only a minor subset of what the API contains. Feel free to explore the rest of the API by yourself.

Benjamin is Software Engineer, Full Stack Developer at Pondus, an excited runner and table foosball player. Get in touch on Twitter and GitHub.

Read More