Monday, March 15, 2010

Google Collections Goodness

I recently got permission at work to add Google Collections into our project.  There are a ton of great features of this library, so I wanted to share a couple of my favorites here.

Multimaps

Ever find yourself writing code like this?

import java.util.*;
public void doOldSchoolMultimap() {
    Map<String, List<Integer>> oddEvenMap = new HashMap<String, List<Integer>>();
    for (int i = 0; i < 100; i++) {
        if (i % 2 == 0) {
            String evenKey = "Even";
            if (!oddEvenMap.containsKey(evenKey)) {
                List<Integer> list = new ArrayList<Integer>();
                oddEvenMap.put(evenKey, list);
            }
            oddEvenMap.get(evenKey).add(i);
        } else {
            String oddKey = "Odd";
            if (!oddEvenMap.containsKey(oddKey)) {
                List<Integer> list = new ArrayList<Integer>();
                oddEvenMap.put(oddKey, list);
            }
            oddEvenMap.get(oddKey).add(i);
        }
    }

    for (Map.Entry<String, List<Integer>> entry : oddEvenMap.entrySet()) {
        for (Integer integer : entry.getValue()) {
            System.out.println("key=" + entry.getKey() + "; integer = " + integer);
        }
    }
}

This is a contrived example where we find all even numbers between 1-100 and put them into a List inside a Map, with "Even" as the key. Ditto for the odd numbers.

Now how would you like to replace it with this code instead?

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;

public void doGoogleMultimap() {
    ListMultimap<String, Integer> oddEvenMap = ArrayListMultimap.create();
    for (int i = 0; i < 100; i++) {
        if (i % 2 == 0) {
            oddEvenMap.put("Even", i);
        } else {
            oddEvenMap.put("Odd", i);
        }
    }

    for (Map.Entry<String, Collection<Integer>> entry : oddEvenMap.asMap().entrySet()) {
        for (Integer integer : entry.getValue()) {
            System.out.println("key=" + entry.getKey() + "; integer = " + integer);
        }
    }
}

Notice a couple of things about the second code block:
  1. No more checking to see if the List exists, then creating the List, etc.  The Google Multimap takes care of that for you.
  2. You only had to specify the generics on the left side, not on both sides.  Using the static factory method to create the ArrayListMultimap reduces the clutter considerably.
  3.  
     

Predicates

One of the features I have used to good effect in the past from Apache Commons Collections were the Predicate classes. Unfortunately, Commons Collections are not written for Java 1.5, so they don't have generics support. Google Collections to the rescue. Here's an example where we find all words that start with "Z" in a List of Strings.
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;

public void doPredicateSearch() {
    Predicate<String> zWordFilter = new Predicate<String>() {
        public boolean apply(String s) {
            return s.toUpperCase().startsWith("Z");
        }
    };
    List<String> words = Lists.newArrayList("Able", "Baker", "Zulu");
    Collection<String> zWords = Collections2.filter(words, zWordFilter);
    for (String zWord : zWords) {
        System.out.println("zWord = " + zWord);
    }
}

Again, this is a very simplistic example, but the apply() method can be arbitrarily complex.  The Predicate object can also be reused as a function.  In fact, this is a step towards functional programming in Java. 

There are additional "pre-built" Predicates available from the Predicates class that allow you create compound predicates that can do even more.

Conclusion

Hopefully this quick overview has gotten you interested in Google Collections as a way to improve your Java code.

3 comments:

  1. Nice post. I think features like the ones you described are implemented nicely in languages like Scala.

    Examples above rewritten in Scala

    1)
    val list = (1 to 20).toList
    var result = list.groupBy[String](groupOddOrEven(_))

    def groupOddOrEven(value: Int): String = value match
    {
    case i if (value % 2 == 0) => "even"
    case _ => "odd"
    }

    2)
    var names = List("Able","Baker","Zulu")
    var result = nameList filter(_.startsWith("Z"))
    println(result)

    ReplyDelete
  2. Dan, thanks for your comment. I completely agree with your views on Scala (a language I haven't learned yet but am interested in). I have done some work in Groovy, and the code would be similarly concise if written in that language.

    But for those of us stuck using Java for the foreseeable future, Google Collections is a breath of fresh air.

    ReplyDelete
  3. I think this another one that attempts to bring functional style to Java.. http://functionaljava.org/

    ReplyDelete