September 14, 2009

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.

1.) 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

2.) 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

3.) 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

4.) 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]


5.) 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. :-)

Related Links:

20 comments:

Kevin Jordan said...

Any ideas on benchmarks on some of these? I'd in particular be curious on how fast the filtering is on a large collection.

Benjamin Winterberg said...

Hi Kevin,

I'm using Google Collections in productive code for a few month now and didn't find any performance issues. However I didn't use the API for performance critical scenarious so far.

Code Style said...

This page does not print well in Firefox 3.5 -- only the first page is rendered.

Benjamin Winterberg said...

Please can you explain the problem any further? I'm using Firefox 3.5.3 and don't see any problems on WinXP or Vista.

Nacho Coloma said...

I am missing a reference to the great Multimap classes (ListMultimap et al).

translucent_eye said...

Thanks for this. I wasn't familiar with Google's Collections prior to this, but am a big user of Java collections. I can see many ways in which this can speed up writing of code, and other ways it which it can make thing much clearer.

I hope to take their Collections out for a spin soon....looking forward to it.

David Sachdev
http://www.translucent-development.com
http://www.translucent-design.com

skormos said...

Aside from generics, how is this better or worse than Apache Commons Collections?

André Faria Gomes said...

Nice post. Really usefull!

stevenhong said...

the collection still in RC state?

Benjamin Winterberg said...

Yes, it's in RC2 for the moment. But from my experience it's very stable:

"Until the release is final, the API is still subject to change. (However, these changes should be very minimal at this point.)"

Leslie Prabakar Ruphas V said...

Should appreciate you for this wonderful stuff.

Google competes with all and gets success factors come true too.

Thanks,
Leslie V
www.googlestepper.blogspot.com
www.scrollnroll.blogspot.com

Kevin Bourrillion said...

Author -- thanks very much for helping to spread the word about our library!

Kevin Jordan - creating the filter is O(1) - it's just a view. But perhaps you mean a comparison of iterating over that filtered collection versus iterating over the source collection with an if() statement inside the loop. That might be an interesting benchmark to write.

So far, our priority attention has not been on micro-optimizing these libraries. But you can review the code to see that we're at least not doing anything idiotic. (In most cases anyway!)

Benjamin Winterberg said...

You're welcome, Kevin! Keep up the good work. :)

Kevin Bourrillion said...

Ben, I forgot the other thing I meant to say --

Instead of creating a new Comparator<>() and then passing that to Ordering.from(), just extend Ordering right from the start. Much simpler. No reason to use plain Comparator anymore!

n said...

Thanks for the great post.

Your ImmutableMap example can be simplified further, this is often just a one-liner:

ImmutableMap[String,Integer] map1 = ImmutableMap.of("one", 1, "two", 2, "three", 3);

(Note that I'm using [ ] instead of greater/less than signs, due to blogger's comment limitations.)

Deepak said...

Hi,

Tried explaining google collection types in this article http://www.myhomepageindia.com/index.php/2010/01/07/google-collection-types.html

Rajesh said...

Nice post. Thanks for that. I will try to learn more on this.

Jabes Felipe said...

That's great!

Фёдор Ананьев said...

Thank you for cool explanation of all this stuff :-)

Hima P said...

Nice post. Really usefull!