Exploring Google Guava

Exploring Google Guava

By Dan Lewis, OCI Senior Software Engineer

April 2010


Introduction

Google recently announced the public availability of Guava, a Java utility library previously available only internally at Google. Guava provides building blocks that build on the existing Java libraries and result in productivity aids for Java programmers. Guava also serves as an example of good Java coding idioms. Guava subsumes the Google Collections library and adds additional packages covering general-purpose utilities, input/output, primitives, and concurrency. My previous Java News Brief articles about Google Collections are listed in the references section at the end of this article. This article will cover almost every Class and Interface added to Google Guava since the Google Collections 1.0 release in a "definition list" format, grouped by package. Selected classes are utilized in demonstration examples. Because Google Guava, as of this writing, is distributed via Subversion repository access only, instructions are included at the end of the article for obtaining and building your own copy.

The Guava Packages

There are six Java packages in the Guava library:

1. com.google.common.annotations
annotations used by the other packages
2. com.google.common.base
common code used by the other packages, including all of the former Google Collections Library and some additional classes
3. com.google.common.collect
extensions of the Java collections framework, including all of the former Google Collections library and some additional classes
4. com.google.common.io
utility classes used for easing input/output
5. com.google.common.primitives
utility classes used when working with Java primitive types
6. com.google.common.util.concurrent
utility classes used when doing concurrent programming

Guava Package 2: com.google.common.base

This package was included with Google Collections 1.0. Since that release, the following classes, interfaces, and enums are new:

CaseFormat
This enum defines and converts between some naming conventions commonly used in Java and C++ source code: LOWER_HYPHEN, LOWER_UNDERSCORE, LOWER_CAMEL, UPPER_CAMEL, and UPPER_UNDERSCORE
CharMatcher
Matches primitive chars and CharSequence objects. This is used heavily in the Splitter class described later.
Charsets
Provides constant definitions for the standard Charset objects, so you don't need to use String constants in your code to obtain them anymore.
Defaults
Contains default values for built-in Java types.
Service
This interface generalizes the concept of a service that can be started and stopped via asynchronous method calls and whose state can be inspected in a thread-safe way.
Splitter
This class is a utility class for splitting Strings based on various conditions, the natural complement to Joiner from Google Collections and the spiritual successor to strtok() and StringTokenizer. In the example below, observe that I start with the static method on() and then call "builder" methods to get the Splitter we want before finally calling split(). I use the trimResults() and omitEmptyStrings() builder options to obtain the desired results.
  1. package com.ociweb.jnb.apr2010;
  2.  
  3. import com.google.common.base.Splitter;
  4.  
  5. public class ExerciseSplitter {
  6. public static void main(String[] args) {
  7. final String input = "Alpha, Bravo,,Charlie ,Delta,Echo";
  8. printWords(Splitter.on(',').split(input));
  9. System.out.println();
  10. printWords(Splitter.on(',').trimResults().split(input));
  11. System.out.println();
  12. printWords(Splitter.on(',').trimResults().omitEmptyStrings().split(input));
  13. }
  14.  
  15. private static void printWords(Iterable<String> words) {
  16. for (String word : words) {
  17. System.out.printf("[%s]\n", word);
  18. }
  19. }
  20. }

And the output is:

[Alpha]
[ Bravo]
[]
[Charlie ]
[Delta]
[Echo]
 
[Alpha]
[Bravo]
[]
[Charlie]
[Delta]
[Echo]
 
[Alpha]
[Bravo]
[Charlie]
[Delta]
[Echo]
 
Throwables
This is a utility class that contains static methods for dealing with Exceptions (Throwables).

The propagate() method (demonstrated below) will throw the argument Throwable unchanged if it is unchecked (i.e. RuntimeException or Error) or wrap it in a RuntimeException and throw it. This is useful if you are doing several operations that might throw exceptions but do not want to force callers to deal with checked exceptions.
  1. package com.ociweb.jnb.apr2010;
  2.  
  3. import com.google.common.base.Throwables;
  4.  
  5. import java.io.InputStream;
  6. import java.net.URL;
  7.  
  8. public class ExerciseThrowables {
  9. public static void main(String[] args) {
  10. try {
  11. URL url = new URL("http://ociweb.com");
  12. final InputStream in = url.openStream();
  13. // read from the input stream
  14. in.close();
  15. } catch (Throwable t) {
  16. throw Throwables.propagate(t);
  17. }
  18. }
  19. }

Guava Package 3: com.google.common.collect

This package was included with Google Collections 1.0. Since that release, the following classes and interfaces are new:

ComparisonChain
This is a utility class that is helpful for implementing the Comparable interface, e.g., the compareTo() method.

Recall that compareTo() contractually returns -1, 0, +1 as the argument is "less", "equal", or "greater" than "this".
  1. package com.ociweb.jnb.apr2010;
  2.  
  3. import com.google.common.collect.ComparisonChain;
  4. import com.google.common.collect.Sets;
  5.  
  6. import java.util.Set;
  7.  
  8. public class ExerciseComparisonChain {
  9.  
  10. static class Person implements Comparable {
  11. private int birthMonth;
  12. private int birthDayOfWeek;
  13. private int birthYear;
  14. private String firstName;
  15.  
  16. Person(String firstName, int birthMonth, int birthDayOfWeek, int birthYear) {
  17. this.birthMonth = birthMonth;
  18. this.birthDayOfWeek = birthDayOfWeek;
  19. this.birthYear = birthYear;
  20. this.firstName = firstName;
  21. }
  22.  
  23. public int compareTo(Object o) {
  24. Person other = (Person) o;
  25. return ComparisonChain.start().
  26. compare(birthYear, other.birthYear).
  27. compare(birthMonth, other.birthMonth).
  28. compare(birthDayOfWeek, other.birthDayOfWeek).
  29. compare(firstName, other.firstName).
  30. result();
  31. }
  32. }
  33.  
  34. public static void main(String[] args) {
  35. Set<Person> people = Sets.newTreeSet();
  36. people.add(new Person("Abigail", 4, 1, 1980));
  37. people.add(new Person("Courtney", 5, 2, 1981));
  38. people.add(new Person("Chastity", 5, 2, 1981));
  39. for (Person person : people) {
  40. System.out.println(person.firstName);
  41. }
  42. }
  43. }

And the output is:

Abigail
Chastity
Courtney
ImmutableAsList
This class is used internally by ImmutableCollection.asList()
ImmutableSortedAsList
This class is used internally by ImmutableSortedSet.asList()
LexicographicalOrdering
This class is created by calling Ordering.lexicographical(). According to the Javadocs, it returns the "dictionary" ordering. Note that to use it, you need two lists, not one list of Strings.
  1. package com.ociweb.jnb.apr2010;
  2.  
  3. import com.google.common.base.Joiner;
  4. import com.google.common.collect.Lists;
  5. import com.google.common.collect.Ordering;
  6.  
  7. import java.util.Collections;
  8. import java.util.List;
  9.  
  10. public class ExerciseLexicographicalOrdering {
  11. public static void main(String[] args) {
  12. List<String> names1 = Lists.newArrayList("whiskey", "tango", "golf");
  13. List<String> names2 = Lists.newArrayList("whiskey", "tango", "foxtrot");
  14. List<Iterable<String>> namesListList = Lists.newArrayList();
  15. namesListList.add(names1);
  16. namesListList.add(names2);
  17.  
  18. final Ordering<Iterable<String>> lexOrd = Ordering.<String>natural().lexicographical();
  19.  
  20. Collections.sort(namesListList, lexOrd);
  21.  
  22. System.out.println(Joiner.on(',').join(namesListList));
  23.  
  24. }
  25. }

And the output is:

[whiskey, tango, foxtrot],[whiskey, tango, golf]

Guava Package 4: com.google.common.io

This package wasn't part of Google Collections 1.0, so it is new to Guava. It contains the following classes and interfaces:

AppendableWriter
This Writer wraps an Appendable. This is more flexible than StringWriter included with the JDK.
ByteArrayDataInput
This interface extends the java.io.DataInput interface, but the methods don't throw IOException. See static method ByteStreams.newDataInput().
ByteArrayDataOutput
This interface extends the java.io.DataOutput interface, but the methods don't throwIOException. See static method ByteStreams.newDataOutput().
ByteProcessor
This interface is a callback expected by Files.readBytes(). Implement processBytes() to process bytes as they are received. Return true to continue processing or false otherwise. The Files class is described later.
ByteStreams
Utility methods for reading, writing, copying, joining, and hashing InputStreams and OutputStreams (binary data).
CharStreams
Utility methods for reading, writing, copying, joining, and hashing Readers and Writers (character data).
Closeables
Utility methods for closing Closeables (e.g. Streams, Files, etc). In the example below,closeQuietly() is used to close the OutputStream and log any exceptions encountered to a java.util.logging.Logger.
  1. package com.ociweb.jnb.apr2010;
  2.  
  3. import com.google.common.base.Throwables;
  4. import com.google.common.io.Closeables;
  5.  
  6. import java.io.File;
  7. import java.io.FileOutputStream;
  8. import java.io.IOException;
  9. import java.io.OutputStream;
  10.  
  11. public class ExerciseCloseables {
  12. public static void main(String[] args) {
  13. final OutputStream outputStream = newTempFileOutputStream();
  14. Closeables.closeQuietly(outputStream);
  15. }
  16.  
  17. private static OutputStream newTempFileOutputStream() {
  18. try {
  19. final File tmpFile = File.createTempFile(ExerciseCloseables.class.getName(), "tmp");
  20. return new FileOutputStream(tmpFile);
  21. } catch (IOException e) {
  22. throw Throwables.propagate(e);
  23. }
  24. }
  25. }
CountingInputStream
InputStream that provides a getCount() method that returns number of bytes read as a long.
CountingOutputStream
OutputStream that provides a getCount() method that returns number of bytes read as a long.
FileBackedOutputStream
From the Javadocs: "An OutputStream that starts buffering to a byte array, but switches to file buffering once the data reaches a configurable size."
Files
Utility methods for reading from, writing to, and manipulating files and directories.
Flushables
Utility method for flushing Flushables (e.g. Streams, Files, etc).
InputSupplier
This parameterized interface is returned by several static utility methods in Files, ByteStreams, CharStreams, and Resources. Parameter T is usually an InputStream or Reader(or subclass).
LimitInputStream
This InputStream descendant is constructed with an integer that limits the maximum number of bytes that can be read. Attempting to read beyond returns -1, which typically means end of stream.
LineBuffer
This class is an implementation detail of LineReader.
LineProcessor
This interface is a callback passed as argument to CharStreams.readlines(),Files.readLines(), and Resources.readLines(). The processLine() method is called back for each line, and you should return false to stop processing. Finally, getResult() may be called to return the result of processing all the lines. The type of the return is type parameter T.
LineReader
This is a more flexible version of BufferedReader that works for all implementers ofReadable, not just Reader. For example, java.io.CharBuffer subclasses are supported.
MultiInputStream
This class is used internally by ByteStreams.join() which allows multiple InputStreams to be "concatenated" or read serially, one after the other, as if they were one stream.
MultiReader
This class is used internally by CharStreams.join() which allows multiple Readers to be "concatenated" or read serially, one after the other, as if they were one reader.
NullOutputStream
This OutputStream subclass ignores all bytes. i.e. the "bit bucket".
OutputSupplier
This parameterized interface is returned by several static utility methods in Files, ByteStreams, CharStreams, and Resources. Parameter T is usually an OutputStream or Writer (or subclass).
PatternFilenameFilter
This implementation of java.io.FilenameFilter uses a regular expression (passed to constructor) to filter files.
Resources
The static utility methods in this class are useful for reading "Resources", or various non-code files that may be bundled with an application, e.g. data files, images, etc.

Guava Package 5: com.google.common.primitives

This package wasn't part of Google Collections 1.0 so is new to Guava. It contains the following classes and interfaces:

BooleansBytesCharsDoublesFloatsIntsLongsShortsSignedBytesUnsignedBytes
These classes provides static utility methods for manipulating individual primitives as well as primitives arrays, including searching, concatenating, wrapping and unwrapping. For the integral types (int, short, char byte) there are checkedCast and saturatedCast methods in the integral types that will convert a long to the given type. checkedCast will throw an exception if the long is too large or too small, but saturatedCast will return the closest value.
Primitives
This class converts between primitive and wrapper meta-classes.
  1. package com.ociweb.jnb.apr2010;
  2.  
  3. import com.google.common.primitives.Primitives;
  4.  
  5. public class ExercisePrimitives {
  6. public static void main(String[] args) {
  7. System.out.println(Primitives.isWrapperType(Integer.class));
  8. System.out.println(Primitives.isWrapperType(int.class));
  9. final Class<Integer> clazz = Integer.class;
  10. System.out.println(clazz);
  11. final Class<Integer> unwrapped = Primitives.unwrap(clazz);
  12. System.out.println(unwrapped);
  13. final Class<Integer> unwrappedTwice = Primitives.unwrap(unwrapped);
  14. System.out.println(unwrappedTwice);
  15. final Class<Integer> rewrapped = Primitives.wrap(unwrappedTwice);
  16. System.out.println(rewrapped);
  17. }
  18. }

And the output is:

true
false
class java.lang.Integer
int
int
class java.lang.Integer

Guava Package 6: com.google.common.util.concurrent

This package wasn't part of Google Collections 1.0, so it is new to Guava. It contains the following classes and interfaces:

AbstractCheckedFuture
This class wraps a Future, provided in the constructor, with the checkedGet methods specified in CheckedFuture.

To extend this class you need to implement the mapException method that translates each of the three exceptions: InterruptedExceptionCancellationException, and ExecutionException.

Consider the following example that always maps all exceptions to CustomException:
  1. package com.ociweb.jnb.apr2010;
  2.  
  3. import com.google.common.util.concurrent.AbstractCheckedFuture;
  4. import com.google.common.util.concurrent.Futures;
  5. import com.google.common.util.concurrent.ListenableFuture;
  6.  
  7. import java.util.concurrent.*;
  8.  
  9. public class ExerciseAbstractCheckedFuture<V> extends AbstractCheckedFuture<V,
  10. ExerciseAbstractCheckedFuture.CustomException> {
  11.  
  12. public ExerciseAbstractCheckedFuture(ListenableFuture<V> delegate) {
  13. super(delegate);
  14. }
  15.  
  16. @Override
  17. protected CustomException mapException(Exception e) {
  18. return new CustomException(e);
  19. }
  20.  
  21. public static class CustomException extends Exception {
  22. public CustomException(Throwable cause) {
  23. super(cause);
  24. }
  25. }
  26.  
  27. public static void main(String[] args) {
  28. final Callable<Long> callable = new Callable<Long>() {
  29. public Long call() throws Exception {
  30. if (System.currentTimeMillis() % 2 == 0) {
  31. throw new RuntimeException();
  32. }
  33. return 100L;
  34. }
  35. };
  36. final ExecutorService executorService = Executors.newSingleThreadExecutor();
  37. final Future<Long> future = executorService.submit(callable);
  38. final ExerciseAbstractCheckedFuture<Long> checkedFuture =
  39. new ExerciseAbstractCheckedFuture<Long>(Futures.makeListenable(future));
  40. try {
  41. final long value = checkedFuture.checkedGet();
  42. System.out.println("value=" + value);
  43. } catch (CustomException e) {
  44. e.printStackTrace();
  45. }
  46. executorService.shutdown();
  47. }
  48. }

And the output is sometimes:

value=100

But other times it's a nasty stack trace starting with our CustomException.

AbstractExecutionThreadService
This abstract class would be a good starting point for building a custom threaded service. At a minimum, override the run method.
AbstractFuture
This abstract class provides a starting point to implement a Future that is not based on Runnables. You simply override get() and you are on your way, with thread safety built-in.
AbstractIdleService
This abstract class can be used to implement services that do something on startup and shutdown, but otherwise don't do anything.
AbstractListenableFuture
This is a base class that can be used to create a Future that also implements the ListenableFuture interface. See the Futures.makeListenable() utility method.
AbstractService
This is a base class that can be used to create a Service by implementing doStart() and doStop().
Callables
This class contains static utility methods for working with Callables. So far, the only method available is returning(), which always returns the same value immediately.
CheckedFuture
This sub-interface of ListenableFuture adds two getChecked() methods that return an Exception subclass of the parameterized type E. It also, through ListenableFuture supports notification when the value is available.
DaemonThreadFactory
This implementation of ThreadFactory wraps a given ThreadFactory, then uses it to create threads, but before returning those threads calls setDaemon(true) on them.
ExecutionList
This class pairs Runnables with Executors for later execution. This class is used by the implementations of ListenableFuture interface in Guava but is public for broader use.
Executors
This class contains static utility methods mostly pertaining to ThreadPoolExecutors and their behavior during JVM shutdown. sameThreadExecutor() creates an ExecutorService that runs all tasks in the same thread as execute and submit(). daemonThreadFactory()and variant can be used to create daemon threads.
FakeTimeLimiter
This is an implementation of TimeLimiter interface used for unit testing.
ForwardingFuture
This interface is implemented within Futures.makeListenable() to convert any Future into a ListenableFuture.
ForwardingService
This Service delegates to another service. Was apparently used in an earlier iteration of Guava but not used as of this writing.
Futures
Futures provides some utility methods for chaining and composing Futures with Functions. Also provided are the makeChecked(), makeListenable(), makeUninterruptable() which can take a Future and make it checked, listenable, or uninterruptable, respectively. Could a FutureBuilder be in Guava's future?
ListenableFuture
This interface extends Future with an addListener() method that accepts both a Runnable listener and an Executor on which to execute the listener.
ListenableFutureTask
This class extends FutureTask and adds the ability to register listeners.
NamingThreadFactory
This is a ThreadFactory that can name threads by injecting the thread number into a given String pattern. This can reduce some boilerplate code
SimpleTimeLimiter
This is the implementation of TimeLimiter provided with Guava. Execution of proxy target methods happen in an ExecutorService that is provided during construction. If none is provided by caller, a Executors.newCachedThreadPool() is used.
TimeLimiter
This creates a proxy class that limits duration of all method calls on the target object.
UncheckedTimeoutException
This is an unchecked exception (extending RuntimeException makes it unchecked) that is thrown by SimpleTimeLimiter when a timeout occured.
UninterruptibleFuture
An instance of this interface is returned by Futures.makeUninterruptable(). As expected, this Future continues to execute even if interrupted. If interrupted during execution, it will set the interrupted flag on the current Thread after it has completed executing.
ValueFuture
This implementation of ListenableFuture can be used when result is already known, but something of type ListenableFuture is needed. This is public but used internally in the Futures class.

Licensing

Guava is provided under the Apache open source license. This is considered a non-viral open-source license meaning that you are not required to release your source code even if it consumes Google Guava. Note that open source doesn't mean open commits. The Google project leadership provides strict editorial control over what changes may be made to the library. Guava runs on Java 5 virtual machines and newer.

Obtain Guava

As of this writing, there is no pre-built jar file of Google Guava provided. The best way to get started with Guava is to check it out of the Google Code repository. You will need a Subversion (svn) client for your platform. The following command will check out the latest version:

svn checkout http://guava-libraries.googlecode.com/svn/trunk/ guava

What's in the box?

When you check out Guava, you'll see a root and three top level directories: javadoc, lib, and src.

root directory
Ant build.xml file, Apache 2.0 license, and IntelliJ IDEA project metadata
javadoc
the API documentation for Guava
lib
any 3rd-party libraries used by Guava, currently only jsr305.jar, the annotations for software defect detection
src
the source code for Guava

The Ant build file.

To build Guava, set the JAVA5_HOME environment variable. Run something like the following command, substituting the location of your Java installation:

set JAVA5_HOME=C:\Program Files\Java\jdk1.6.0_18

Support for at least Java 5 is enforced in the build script. I used Sun's Java 6 update 18 JDK to compile with impunity.

There are only three targets: compile (default), javadoc, and clean.

The compile target will dump everything into build/classes.

The javadoc target will output to build/javadoc. You can override the Java property "version" (default value "snapshot") to affect the window titles in the javadocs.

Apparently Google internally uses its own JDK, as evidenced by the metadata present in the javadocs. Inside the original allclasses-frame.html you can see:

<!-- Generated by javadoc (build 1.6.0-google-internal) on Mon Jan 04 20:48:01 PST 2010 -->

but in my generated version you can see:

<!-- Generated by javadoc (build 1.6.0_18) on Sat Jan 16 11:11:38 CST 2010 -->

Conclusion

Guava can improve the productivity of any Java programmer who takes the time to learn the library. It does so in three ways.

First, it provides useful utilities to reduce the usage of error-prone code. For example, Charsets provides constants required to be part of every JDK. Otherwise, one would be forced to use String literals.

Second, it builds on existing JDK libraries to give added flexibility and power. For example PatternFilenameFilter, which brings together Regular expressions with java.io.FilenameFilter. Further examples are Splitter and Joiner. Splitter corrects many of the deficiencies found in the String.split() family of methods and takes advantage of the Builder pattern. JoinerSplitter's complement, eases the task of creating delimited Strings from String Arrays and Collections.

Finally, Guava is written by some very talented, yet pragmatic programmers and expresses the state-of-the-art of Java programming best practices and idioms.

For example, the builder pattern is used in the Splitter class. This has a practical effect of making a very common task, tokenizing Strings, easier, yet more flexible than ever before.

Another example is Throwables. Many times a developer simply doesn't know what to do with a checked exception and prints it to stdout, and happily moves on. Logged statements are easily missed or eaten by application frameworks. Throwables force the issue by at once making the code cleaner and easier to follow, and simultaneously throwing an unchecked exception to a higher level which will usually cause an application to "fail fast". The net result is an application developer that observes an application error dialog immediately upon encountering a defect, rather than strange behavior at a point later in the program.

For these reasons, Google Guava will reward the developer that learns it, even if she is unable to utilize it in production code at this time. Because Guava is in a very early stage, the API's may change. So, if you choose to incorporate Guava into your application now, please recognize that you will likely need to modify code to support later snapshots and releases of Guava.

References



Software Engineering Tech Trends (SETT) is a monthly newsletter featuring emerging trends in software engineering.

Check out OUR MOST POPULAR articles!