Better Java with Google Collections

Version 1.0 of the Google Collections library was officially released December 30, 2009. I have been using the library for the past 6 months or so on a variety of Java projects, with great success. Today I gave a brown-bag talk to share with some interested Atoms.

I have included below the sample code I used as presentation material, in case others are interested.

Note: As I was putting this post together I read that Google Collections is now part of Google’s guava-libraries project, which has not been officially released yet.

package com.atomicobject.misc;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import org.junit.Test;

import java.util.Collection;
import java.util.List;
import java.util.Map;

import static junit.framework.Assert.assertEquals;

/**
 * Google Collections Library
 *
 * A better way to write Java code. The library guides you towards using
 * a number of functional concepts and techniques like immutable
 * collections and first class functions. The convenience methods for
 * creating collections are EXTREMELY useful in unit tests.
 */
@SuppressWarnings({"UnusedDeclaration"})
public class DemoGoogleCollectionsTest {

  /**
   * Easy to make new collections
   */
  public void makeNewCollectionsInline() {
    {
      // Don't need to specify generic types on right hand side
      List names = Lists.newArrayList();
      names.add("Joe");
    }

    {
      // Empty immutable list
      List names = ImmutableList.of();
    }

    {
      // Immutable list built in one line of code
      List names = ImmutableList.of("Joe", "Tom");
    }

    {
      // Convenient map creation
      Map people = ImmutableMap.of(1, "Joe",
                                                    2, "Tom");
    }
  }

  /**
   * Builders for more complicated collections. Good for building static
   * final collections.
   */
  @Test
  public void buildersForNewCollections() {
    {
      List otherNames = ImmutableList.of("Tom", "Jerry");

      List names = new ImmutableList.Builder()
          .add("Joe")
          .addAll(otherNames)
          .build();

      assertEquals(ImmutableList.of("Joe", "Tom", "Jerry"), names);
    }

    {
      new ImmutableMap.Builder()
          .put("Joe", "100 Michigan St.")
          .put("Frank", "1010 Fulton St.")
          .build();
    }

    {
      // Sometimes need to iterate over something to build up a map.
      List names = ImmutableList.of("Joe", "Henry");
      ImmutableMap.Builder mapBuilder =
          new ImmutableMap.Builder();

      for (String name : names) {
        mapBuilder.put(name, name.length());
      }

      // Return an immutable map that was built during the iteration
      Map result = mapBuilder.build();

      assertEquals(ImmutableMap.of("Joe", 3, "Henry", 5), result);
    }
  }

  /**
   * Transforming a collection. Transformation is lazy - only happens when
   * an item in the list is accessed. The transformation will take place
   *  every time
   * an item is accessed from the new list though.
   */
  @Test
  public void transformation() {
    // Transform list
    {
      List names = ImmutableList.of("Joe", "Frank");
      List nameLengths =
          Lists.transform(names, new Function() {
        public Integer apply(String name) {
          return name.length();
        }
      });
      assertEquals(ImmutableList.of(3, 5), nameLengths);
    }

    // Transform Collection
    {
      Collection numbers = ImmutableSet.of(1, 2, 3, 4);
      Collection strings =
          Collections2.transform(numbers, new Function() {

        public String apply(Integer number) {
          return String.valueOf(number);
        }
      });
      // NOTE: due to laziness, sometimes need to force the transformation in
      //       in assertions by using copyOf.
      assertEquals(ImmutableList.of("1", "2", "3", "4"),
                   ImmutableList.copyOf(strings));
    }
  }

  /**
   * Filter collections using a predicate
   */
  @Test
  public void filtering() {
    {
      List names = ImmutableList.of("Joe", "Frank", "Henry");
      Iterable threeLetterNames =
          Iterables.filter(names, new Predicate() {
            public boolean apply(String name) {
              return name.length() == 3;
            }
          });

      // Want to compare the result to a list so make
      // Immutablelist copy for assertEquals to use     
      assertEquals(ImmutableList.of("Joe"),
                   ImmutableList.copyOf(threeLetterNames));
    }
  }

  /**
   * Join strings together
   */
  @Test
  public void joinStrings() {
    {
      List names = ImmutableList.of("Joe", "Frank", "Henry");      
      String combined = Joiner.on(", ").skipNulls().join(names);
      assertEquals("Joe, Frank, Henry", combined);
    }
  }

  /**
   * Sorting collections
   */
  @Test
  public void sortTheStrings() {
    List names = ImmutableList.of("Joe", "Frank", "Henry");

    // Sort by natural sorting order of object
    {
      List sortedNames = Ordering.natural().sortedCopy(names);
      assertEquals(ImmutableList.of("Frank", "Henry", "Joe"), sortedNames);
    }

    // Custom sort order
    {
      Ordering secondLetterOrdering = new Ordering() {
        public int compare(String s1, String s2) {
          return s1.substring(1).compareTo(s2.substring(1));          
        }
      };

      List sortedNames = secondLetterOrdering.sortedCopy(names);
      assertEquals(ImmutableList.of("Henry", "Joe", "Frank"), sortedNames);
    }
  }

  /**
   * Multimaps can hold multiple values for the same key.
   */
  @Test
  public void multimaps() {
    Multimap cars = LinkedHashMultimap.create();
    cars.put("Toyota", "Corolla");
    cars.put("Toyota", "Prius");
    cars.put("Jeep", "Grand Cherokee");

    assertEquals(ImmutableSet.of("Corolla", "Prius"), cars.get("Toyota"));
    assertEquals(ImmutableSet.of("Grand Cherokee"), cars.get("Jeep"));
  }

  /**
   * Forwarding base classes. Very useful when you want a collection
   * class with special functionality. You just need to implement
   * a delegate function, and then override only the other methods
   * you need.
   */
  private static class EmptyMap extends ForwardingMap {

    @Override
    protected Map delegate() {
      return ImmutableMap.of();
    }
  }

  /**
   * Miscellaneous stuff
   */
  @Test
  public void miscellaneous() {
    // Setup
    Predicate predicate = Predicates.alwaysTrue();
    List names = ImmutableList.of("Joe", "Henry", "Frank");

    // Sometimes Java isn't smart enough, so have to specify type
    formatString(ImmutableList.of());

    // Returns true if all items match the predicate
    Iterables.all(names, predicate);

    // Returns true if any items match the predicate
    Iterables.any(names, predicate);

    // Combine multiple Iterables
    Iterables.concat(names, names, names);

    // True if contains item
    Iterables.contains(names, "Joe");

    // Partition an iterable into specified size chunks
    Iterable> chunks = Iterables.partition(names, 2);

    Iterables.reverse(names);

    Iterables.toString(names);    
  }


  private void formatString(List strings) { }

}