Thứ Hai, 21 tháng 11, 2011

Two Generally Useful Guava Annotations

Guava currently (Release 10) includes four annotations in its com.google.common.annotations package: Beta, VisibleForTesting, GwtCompatible, and GwtIncompatible. The last two are specific to use with Google Web Toolkit (GWT), but the former two can be useful in a more general context.

The @Beta annotation is used within Guava's own code base to indicate "that a public API (public class, method or field) is subject to incompatible changes, or even removal, in a future release." Although this annotation is used to indicate at-risk public API constructs in Guava, it can also be used in code that has access to Guava on its classpath. Developers can use this annotation to advertise their own at-risk public API constructs.

The @Beta annotation is defined as a @Documented, which means that it marks something that is part of the public API and should be considered by Javadoc and other source code documentation tools.

The @VisibleForTesting annotation "indicates that the visibility of a type or member has been relaxed to make the code testable." I have never liked having to relax type or member visibility to make something testable. It feels wrong to have to compromise one's design to allow testing to occur. This annotation is better than nothing in such a case because it at least makes it clear to others using the construct that there is a reason for its otherwise surprisingly relaxed visibility.

Conclusion

Guava provides two annotations that are not part of the standard Java distribution, but cover situations that we often run into during Java development. The @Beta annotation indicates a construct in a public API that may be changed or removed. The @VisibleForTesting annotation advertises to other developers (or reminds the code's author) when a decision was made for relaxed visibility to make testing possible or easier.

Thứ Hai, 14 tháng 11, 2011

Effective Javadoc Documentation Illustrated in Familiar Projects

Three years ago, I wrote about practices that I believe lead to more effective Javadoc in my post More Effective Javadoc. In this post, I look at some familiar projects which provide good examples of effective Javadoc documentation practices. I, of course, will only be covering a very tiny representative sample of the many good projects and many good Javadoc ideas that are out there.

1. Advertising Ultimate Demise of Deprecated Method (Guava)

The current version of Guava (Release 10) provides some good examples of more informative statements of deprecation. The next screen snapshot shows the @deprecated text for methods Files.deleteDirectoryContents(File) and Files.deleteRecursively(File). In both methods' cases, the documentation states why the method is deprecated and, most refreshingly, states when it is envisioned that the method will be removed (Release 11 in these cases). I like the idea of stating in the deprecation statement when the deprecated thing is going away. It is easy to learn to ignore @deprecated and @Deprecated if one believes they are really never going to go away. Stating a planned removal version or date implies more urgency in not using deprecated features and provides fair warning to users.

Although the source code for each of these methods employs the @Deprecated annotation, the code from both cases does not specify this text with Javadoc's @deprecated, but instead simply specifies the deprecation details as part of the normal method description text with bold tags around the word "Deprecated." I'm not sure why this was done instead of using the Javadoc tag explicitly intended for this purpose.

2. Documenting Use of an API (Java SE, Java EE, Guava, Joda Time)

When learning how to use a new API, it is helpful when the Javadoc documentation provides examples of using that API. I first learned how to marshal and unmarshal JAXB objects by reading the Javadoc documentation for Marshaller and Unmarshaller respectively. Both of these classes take advantage of class-level documentation to describe how to use the class's APIs.

Guava's class-level description for Stopwatch shows how to use most of that class's features in a concise and easily understandable class usage description.

Use of an API can be documented at the method level as well as at the class level. Examples of this are Guava's Throwables.propagateIfInstanceOf method and the overloaded Throwables.propagateIfPossible methods . The Javadoc documentation for these methods shows "example usage" for each.

API documentation is not limited to the class level or method level. The javax.management package-level documentation provides a nice overview of Java Management Extensions (JMX). The first sentence of the package description (which is what's always shown at top) is simple enough: "Provides the core classes for the Java Management Extensions." However, there are far more details in the rest of the package description. The next screen snapshot shows a small portion of that package documentation.

Another example of a useful package-level description is the package description for Joda Time package org.joda.time. This core package describes many of the concepts applicable to the entire project in one location.

3. Explicitly Declaring Throws Clause for Unchecked Exceptions (Guava)

In my post More Effective Javadoc, I stated that it is best to "document all thrown exceptions" whether they are checked or unchecked. Guava's InetAddresses.forString(String) method's documentation does this, specifying that it throws the runtime exception IllegalArgumentException.

4. Using -linksource (JFreeChart, Guava)

For an open source project, a nice benefit that can be provided to developers using that project is to allow linking of Javadoc documentation to underlying source code. The next screen snapshots indicate this for JFreeChart and Guava. There are two images for each project, with the first image showing the Javadoc with link to source code annotated and the second image showing the source code displayed when the class name is clicked on in the Javadoc.

It is very convenient to be able to move easily between the Javadoc documentation and the source code. Of course, this can also be done in an IDE that supports Javadoc presentation in conjunction with code.

Conclusion

This post has highlighted several familiar projects who Javadoc documentation provides examples of more effective Javadoc-based documentation.

Thứ Năm, 10 tháng 11, 2011

Speaking at RMOUG Training Days 2012

The Rocky Mountain Oracle Users Group (RMOUG) has announced that the keynote speaker at RMOUG Training Days 2012 will be Cary Millsap of Method R Consulting. RMOUG Training Days 2012 are scheduled for 14-16 February 2012 at the Colorado Convention Center in Denver, Colorado.

I will be speaking at this conference. I will be sole presenter of "JavaFX 2.0: Java in the Rich Internet Application Space" and will be a co-presenter of "Things Developers Wish Managers Knew and Managers Wish Developers Knew." I look forward to returning to the Colorado Convention Center for another edition of RMOUG Training Days.

Thứ Bảy, 5 tháng 11, 2011

Apache Harmony Retiring to the Attic

In a development that seemed destined to happen since IBM's joining OpenJDK, Apache Harmony's Project Management Committee (PMC) has voted 18-2 to move Apache Harmony to the Apache Attic. The Apache Attic page explains the purpose of the Apache Attic: "The Apache Attic was created in November 2008 to provide process and solutions to make it clear when an Apache project has reached its end of life." In short, it has been determined that Apache Harmony has reached its end of life.

Although Apache Harmony enjoyed widespread popularity among numerous Java developers hoping for an open source implementation of standard Java, it was never able to garner support from Sun or Oracle to provide an "independent, compatible implementation of Java SE." With main supporter IBM moving to OpenJDK, this latest development is not too surprising.

Not all projects die once they hit the attic and some live on after retirement. iBatis, for example, lives on as MyBatis (hosted on Google Code). This project "forking" is an example of one of the three approaches for "leaving the attic again." The other two ways of leaving the attic are to return to the Apache Incubator or to recreate a PMC for the project. At this point, my best guess is that none of the three approaches will be used. There is talk on the mailing list of moving it to the public domain, but that also appears unlikely given licensing issues. There has also been hope of Google forking it, but that may be less appealing to Google given the current lawsuit over Android.

With the retirement of Apache Harmony, OpenJDK becomes the sole large open source implementation of Java SE. With the support of Oracle, IBM, Apple, and Twitter (among others), OpenJDK has definitely had the inside track to this position.

Thứ Tư, 2 tháng 11, 2011

Guava's Strings Class

In the post Checking for Null or Empty or White Space Only String in Java, I demonstrated common approaches in the Java ecosystem (standard Java, Guava, Apache Commons Lang, and Groovy) for checking whether a String is null, empty, or white space only similar to what C# supports with the String.IsNullOrWhiteSpace method. One of the approaches I showed was a Guava-based approach that made use of the Guava class Strings and its static isNullOrEmpty(String) method. In this post, I look at other useful functionality for working with Strings that is provided by Guava's six "static utility methods pertaining to String" that are bundled into the Strings class.

Using Guava's Strings class is straightforward because of its well-named methods. The following list enumerates the methods (all static) on the Strings class with a brief description of what each does next to the method name (these descriptions are borrowed or adapted from the Javadoc documentation).

isNullOrEmpty

Guava's Strings.isEmptyOrNull(String) method makes it easy to build simple and highly readable conditional statements that check a String for null or emptiness before acting upon said String. As previously mentioned, I have briefly covered this method before. Another code demonstration of this method is shown next.

Code Sample Using Strings.isNullOrEmpty(String)

/**
* Print to standard output a string message indicating whether the provided
* String is null or empty or not null or empty. This method uses Guava's
* Strings.isNullOrEmpty(String) method.
*
* @param string String to be tested for null or empty.
*/
private static void printStringStatusNullOrEmpty(final String string)
{
out.println( "String '" + string + "' "
+ (Strings.isNullOrEmpty(string) ? "IS" : "is NOT")
+ " null or empty.");
}

/**
* Demonstrate Guava Strings.isNullOrEmpty(String) method on some example
* Strings.
*/
public static void demoIsNullOrEmpty()
{
printHeader("Strings.isNullOrEmpty(String)");
printStringStatusNullOrEmpty("Dustin");
printStringStatusNullOrEmpty(null);
printStringStatusNullOrEmpty("");
}

The output from running the above code is contained in the next screen snapshot. It shows that true is returned when either null or empty String ("") is passed to Strings.isNullOrEmpty(String).

nullToEmpty and emptyToNull

There are multiple times when one may want to treat a null String as an empty String or wants present a null when an empty String exists. In cases such as these when transformations between null and empty String are desired, The following code snippets demonstrate use of Strings.nullToEmpty(String) and Strings.emptyToNull(String).

nullToEmpty and emptyToNull

/**
* Print to standard output a simple message indicating the provided original
* String and the provided result/output String.
*
* @param originalString Original String.
* @param resultString Output or result String created by operation.
* @param operation The operation that acted upon the originalString to create
* the resultString.
*/
private static void printOriginalAndResultStrings(
final String originalString, final String resultString, final String operation)
{
out.println("Passing '" + originalString + "' to " + operation + " produces '" + resultString + "'");
}

/** Demonstrate Guava Strings.emptyToNull() method on example Strings. */
public static void demoEmptyToNull()
{
final String operation = "Strings.emptyToNull(String)";
printHeader(operation);
printOriginalAndResultStrings("Dustin", Strings.emptyToNull("Dustin"), operation);
printOriginalAndResultStrings(null, Strings.emptyToNull(null), operation);
printOriginalAndResultStrings("", Strings.emptyToNull(""), operation);
}

/** Demonstrate Guava Strings.nullToEmpty() method on example Strings. */
public static void demoNullToEmpty()
{
final String operation = "Strings.nullToEmpty(String)";
printHeader(operation);
printOriginalAndResultStrings("Dustin", Strings.nullToEmpty("Dustin"), operation);
printOriginalAndResultStrings(null, Strings.nullToEmpty(null), operation);
printOriginalAndResultStrings("", Strings.nullToEmpty(""), operation);
}

The output from running the above code (shown in the next screen snapshot) proves that these methods work as we'd expect: converting null to empty String or converting empty String to null.

padStart and padEnd

Another common practice when dealing with Strings in Java (or any other language) is to pad a String to a certain length with a specified character. Guava supports this easily with its Strings.padStart(String,int,char) and Strings.padEnd(String,int,char) methods, which are demonstrated in the following code listing.

padStart and padEnd

/**
* Demonstrate Guava Strings.padStart(String,int,char) method on example
* Strings.
*/
public static void demoPadStart()
{
final String operation = "Strings.padStart(String,int,char)";
printHeader(operation);
printOriginalAndResultStrings("Dustin", Strings.padStart("Dustin", 10, '_'), operation);
/* Do NOT call Strings.padStart(String,int,char) on a null String:
* Exception in thread "main" java.lang.NullPointerException
* at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
* at com.google.common.base.Strings.padStart(Strings.java:97)
*/
//printOriginalAndResultStrings(null, Strings.padStart(null, 10, '_'), operation);
printOriginalAndResultStrings("", Strings.padStart("", 10, '_'), operation);
}

/**
* Demonstrate Guava Strings.padEnd(String,int,char) method on example
* Strings.
*/
public static void demoPadEnd()
{
final String operation = "Strings.padEnd(String,int,char)";
printHeader(operation);
printOriginalAndResultStrings("Dustin", Strings.padEnd("Dustin", 10, '_'), operation);
/*
* Do NOT call Strings.padEnd(String,int,char) on a null String:
* Exception in thread "main" java.lang.NullPointerException
* at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
* at com.google.common.base.Strings.padEnd(Strings.java:129)
*/
//printOriginalAndResultStrings(null, Strings.padEnd(null, 10, '_'), operation);
printOriginalAndResultStrings("", Strings.padEnd("", 10, '_'), operation);
}

When executed, the above code pads the provided Strings with underscore characters either before or after the provided String depending on which method was called. In both cases, the length of the String was specified as ten. This output is shown in the next screen snapshot.

repeat

A final manipulation technique that Guava's Strings class supports is the ability to easily repeat a given String a specified number of times. This is demonstrated in the next code listing and the corresponding screen snapshot with that code's output. In this example, the provided String is repeated three times.

repeat

/** Demonstrate Guava Strings.repeat(String,int) method on example Strings. */
public static void demoRepeat()
{
final String operation = "Strings.repeat(String,int)";
printHeader(operation);
printOriginalAndResultStrings("Dustin", Strings.repeat("Dustin", 3), operation);
/*
* Do NOT call Strings.repeat(String,int) on a null String:
* Exception in thread "main" java.lang.NullPointerException
* at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
* at com.google.common.base.Strings.repeat(Strings.java:153)
*/
//printOriginalAndResultStrings(null, Strings.repeat(null, 3), operation);
printOriginalAndResultStrings("", Strings.repeat("", 3), operation);
}
Wrapping Up

The above examples are simple because Guava's Strings class is simple to use. The complete class containing the demonstration code shown earlier is now listed.

GuavaStrings.java

package dustin.examples;

import com.google.common.base.Strings;
import static java.lang.System.out;

/**
* Simple demonstration of Guava's Strings class.
*
* @author Dustin
*/
public class GuavaStrings
{
/**
* Print to standard output a string message indicating whether the provided
* String is null or empty or not null or empty. This method uses Guava's
* Strings.isNullOrEmpty(String) method.
*
* @param string String to be tested for null or empty.
*/
private static void printStringStatusNullOrEmpty(final String string)
{
out.println( "String '" + string + "' "
+ (Strings.isNullOrEmpty(string) ? "IS" : "is NOT")
+ " null or empty.");
}

/**
* Demonstrate Guava Strings.isNullOrEmpty(String) method on some example
* Strings.
*/
public static void demoIsNullOrEmpty()
{
printHeader("Strings.isNullOrEmpty(String)");
printStringStatusNullOrEmpty("Dustin");
printStringStatusNullOrEmpty(null);
printStringStatusNullOrEmpty("");
}

/**
* Print to standard output a simple message indicating the provided original
* String and the provided result/output String.
*
* @param originalString Original String.
* @param resultString Output or result String created by operation.
* @param operation The operation that acted upon the originalString to create
* the resultString.
*/
private static void printOriginalAndResultStrings(
final String originalString, final String resultString, final String operation)
{
out.println("Passing '" + originalString + "' to " + operation + " produces '" + resultString + "'");
}

/** Demonstrate Guava Strings.emptyToNull() method on example Strings. */
public static void demoEmptyToNull()
{
final String operation = "Strings.emptyToNull(String)";
printHeader(operation);
printOriginalAndResultStrings("Dustin", Strings.emptyToNull("Dustin"), operation);
printOriginalAndResultStrings(null, Strings.emptyToNull(null), operation);
printOriginalAndResultStrings("", Strings.emptyToNull(""), operation);
}

/** Demonstrate Guava Strings.nullToEmpty() method on example Strings. */
public static void demoNullToEmpty()
{
final String operation = "Strings.nullToEmpty(String)";
printHeader(operation);
printOriginalAndResultStrings("Dustin", Strings.nullToEmpty("Dustin"), operation);
printOriginalAndResultStrings(null, Strings.nullToEmpty(null), operation);
printOriginalAndResultStrings("", Strings.nullToEmpty(""), operation);
}

/**
* Demonstrate Guava Strings.padStart(String,int,char) method on example
* Strings.
*/
public static void demoPadStart()
{
final String operation = "Strings.padStart(String,int,char)";
printHeader(operation);
printOriginalAndResultStrings("Dustin", Strings.padStart("Dustin", 10, '_'), operation);
/* Do NOT call Strings.padStart(String,int,char) on a null String:
* Exception in thread "main" java.lang.NullPointerException
* at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
* at com.google.common.base.Strings.padStart(Strings.java:97)
*/
//printOriginalAndResultStrings(null, Strings.padStart(null, 10, '_'), operation);
printOriginalAndResultStrings("", Strings.padStart("", 10, '_'), operation);
}

/**
* Demonstrate Guava Strings.padEnd(String,int,char) method on example
* Strings.
*/
public static void demoPadEnd()
{
final String operation = "Strings.padEnd(String,int,char)";
printHeader(operation);
printOriginalAndResultStrings("Dustin", Strings.padEnd("Dustin", 10, '_'), operation);
/*
* Do NOT call Strings.padEnd(String,int,char) on a null String:
* Exception in thread "main" java.lang.NullPointerException
* at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
* at com.google.common.base.Strings.padEnd(Strings.java:129)
*/
//printOriginalAndResultStrings(null, Strings.padEnd(null, 10, '_'), operation);
printOriginalAndResultStrings("", Strings.padEnd("", 10, '_'), operation);
}

/** Demonstrate Guava Strings.repeat(String,int) method on example Strings. */
public static void demoRepeat()
{
final String operation = "Strings.repeat(String,int)";
printHeader(operation);
printOriginalAndResultStrings("Dustin", Strings.repeat("Dustin", 3), operation);
/*
* Do NOT call Strings.repeat(String,int) on a null String:
* Exception in thread "main" java.lang.NullPointerException
* at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
* at com.google.common.base.Strings.repeat(Strings.java:153)
*/
//printOriginalAndResultStrings(null, Strings.repeat(null, 3), operation);
printOriginalAndResultStrings("", Strings.repeat("", 3), operation);
}

/**
* Print a separation header to standard output.
*
* @param headerText Text to be placed in separation header.
*/
public static void printHeader(final String headerText)
{
out.println("\n=========================================================");
out.println("= " + headerText);
out.println("=========================================================");
}

/**
* Main function for demonstrating Guava's Strings class.
*
* @param arguments Command-line arguments: none anticipated.
*/
public static void main(final String[] arguments)
{
demoIsNullOrEmpty();
demoEmptyToNull();
demoNullToEmpty();
demoPadStart();
demoPadEnd();
demoRepeat();
}
}

The methods for padding and for repeating Strings do not take kindly to null Strings being passed to them. Indeed, passing a null to these three methods leads to NullPointerExceptions being thrown. Interestingly, these are more examples of Guava using the Preconditions class in its own code.

Conclusion

Many Java libraries and frameworks provide String manipulation functionality is classes with names like StringUtil. Guava's Strings class is one such example and the methods it supplies can make Java manipulation of Strings easier and more concise. Indeed, as I use Guava's Strings's methods, I feel almost like I'm using some of Groovy's GDK String goodness.

Thứ Hai, 31 tháng 10, 2011

Filtering and Transforming Java Collections with Guava's Collections2

One of the conveniences of Groovy is the ability to easily perform filtering and transformation operations on collections via Groovy's closure support. Guava brings filtering and transformation on collections to standard Java and that is the subject of this post.

Guava's Collections2 class features two public methods, both of which are static. The methods filter(Collection, Predicate) and transform(Collection, Function) do what their names imply: perform filtering and transformation respectively on a given collection. The collection to be filtered or transformed is the first parameter to each static method. The filtering function's second argument is an instance of Guava's Predicate class. The second argument of the transformation function is an instance of Guava's Function class. The remainder of this post demonstrates combining all of this together to filter and transform Java collections.

Filtering Collections with Guava

Filtering collections with Guava is fairly straightforward. The following code snippet demonstrates a simple example of this. A Set of Strings is provided (not shown in the code snippet, but obvious in the output that follows the code) and that provided Set is filtered for only entries beginning with a capital 'J'. This is done via use of Java regular expression support and Guava's Predicates.containsPattern(String), but there are other types of Predicates that can be specified.

Filtering Strings Beginning with 'J'

/**
* Demonstrate Guava's Collections2.filter method. Filter String beginning
* with letter 'J'.
*/
public static void demonstrateFilter()
{
printHeader("Collections2.filter(Collection,Predicate): 'J' Languages");
final Set<String> strings = buildSetStrings();
out.println("\nOriginal Strings (pre-filter):\n\t" + strings);
final Collection<String> filteredStrings =
Collections2.filter(strings, Predicates.containsPattern("^J"));
out.println("\nFiltered Strings:\n\t" + filteredStrings);
out.println("\nOriginal Strings (post-filter):\n\t" + strings);
}

The output from running the above method is shown next. This output shows the lengthy list of programming languages that make up the original Set of Strings returned by buildSetStrings() [source code shown later in the post] and shows the results of the filter call featuring only those programming languages that begin with 'J.'

Transforming Collections with Guava

Transforming collections with Guava works similarly to filtering syntactically, but a Function is used to specify how source collection entries are "transformed" to the output collection rather than using a Predicate to determine which entries to keep. The following code snippet demonstrates transforming entries in a given collection to the uppercase version of themselves.

Transforming Entries to Uppercase

/**
* Demonstrate Guava's Collections2.transform method. Transform input
* collection's entries to uppercase form.
*/
public static void demonstrateTransform()
{
printHeader("Collections2.transform(Collection,Function): Uppercase");
final Set<String> strings = buildSetStrings();
out.println("\nOriginal Strings (pre-transform):\n\t" + strings);
final Collection<String> transformedStrings =
Collections2.transform(strings, new UpperCaseFunction<String, String>());
out.println("\nTransformed Strings:\n\t" + transformedStrings);
out.println("\nOriginal Strings (post-transform):\n\t" + strings);
}

The above transformation code snippet made use of a class called UpperCaseFunction, but you won't find that class in the Guava API documentation. That is a custom class defined as shown in the next code listing.

UpperCaseFunction.java

package dustin.examples;

import com.google.common.base.Function;

/**
* Simple Guava Function that converts provided object's toString() representation
* to upper case.
*
* @author Dustin
*/
public class UpperCaseFunction<F, T> implements Function<F, T>
{
@Override
public Object apply(Object f)
{
return f.toString().toUpperCase();
}
}

The output of running the transformation code snippet that uses the UpperCaseFunction class is shown next.

The above code snippets showed methods devoted to filtering and transforming collections' entries with Guava. The entire code listing for the main class is shown next.

GuavaCollections2.java

package dustin.examples;

import static java.lang.System.out;

import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
* Class whose sole reason for existence is to demonstrate Guava's Collections2
* class.
*
* @author Dustin
*/
public class GuavaCollections2
{
/**
* Provides a Set of Strings.
*
* @return Set of strings representing some programming languages.
*/
private static Set<String> buildSetStrings()
{
final Set<String> strings = new HashSet<String>();
strings.add("Java");
strings.add("Groovy");
strings.add("Jython");
strings.add("JRuby");
strings.add("Python");
strings.add("Ruby");
strings.add("Perl");
strings.add("C");
strings.add("C++");
strings.add("C#");
strings.add("Pascal");
strings.add("Fortran");
strings.add("Cobol");
strings.add("Scala");
strings.add("Clojure");
strings.add("Basic");
strings.add("PHP");
strings.add("Flex/ActionScript");
strings.add("JOVIAL");
return strings;
}

/**
* Demonstrate Guava's Collections2.filter method. Filter String beginning
* with letter 'J'.
*/
public static void demonstrateFilter()
{
printHeader("Collections2.filter(Collection,Predicate): 'J' Languages");
final Set<String> strings = buildSetStrings();
out.println("\nOriginal Strings (pre-filter):\n\t" + strings);
final Collection<String> filteredStrings =
Collections2.filter(strings, Predicates.containsPattern("^J"));
out.println("\nFiltered Strings:\n\t" + filteredStrings);
out.println("\nOriginal Strings (post-filter):\n\t" + strings);
}

/**
* Demonstrate Guava's Collections2.transform method. Transform input
* collection's entries to uppercase form.
*/
public static void demonstrateTransform()
{
printHeader("Collections2.transform(Collection,Function): Uppercase");
final Set<String> strings = buildSetStrings();
out.println("\nOriginal Strings (pre-transform):\n\t" + strings);
final Collection<String> transformedStrings =
Collections2.transform(strings, new UpperCaseFunction<String, String>());
out.println("\nTransformed Strings:\n\t" + transformedStrings);
out.println("\nOriginal Strings (post-transform):\n\t" + strings);
}

/**
* Print a separation header including the provided text.
*
* @param headerText Text to be included in separation header.
*/
private static void printHeader(final String headerText)
{
out.println("\n==========================================================");
out.println("== " + headerText);
out.println("==========================================================");
}

/**
* Main function for demonstrating Guava's Collections2 class.
*
* @param arguments
*/
public static void main(final String[] arguments)
{
demonstrateFilter();
demonstrateTransform();
}
}

Before concluding this post, there is an important caveat to note here. Both methods defined on the Collections2 class contain warnings in their Javadoc documentation about their use. Both methods provide collections that are considered "live views" of the original collections and thus "changes to one affect the other." For example, removing an element from a source collection similarly removes it from the transformed collection. The documentation for each method also warns that neither returns a collection that is Serializable or thread-safe even when the source collection was Serializable and/or thread-safe.

Conclusion

Guava makes it easier to filter collections and transform collections' entries in Java. Although the code may not be as concise as that of Groovy for doing similar things, it beats writing straight Java code without use of Guava's Collections2 class. Java collections can be filtered with Collections2.filter(Collection,Predicate) or transformed with Collections2.transform(Collection,Function).

Immutable Collections, Guava-Style

My general preference is to use immutable classes and collections as often as possible. I have often used the Collections methods for returning unmodifiable collections. One drawback of the "unmodifiable" methods is that they only create "views" of the data structures passed to them and any changes to those underlying structures do change the contents of those "views." In other words, they are only unmodifiable when used directly, but they are still modifiable when their underlying collection is accessed. Guava provides a nice set of "immutable" collections that are more truly immutable or unmodifiable. In Guava's "immutable" collections, changes to the data structure used to populate the "immutable" collection are NOT reflected in the contents of the immutable collection because it is a separate and distinct copy of the data rather than a mere "view." In this post, I look at this difference a little more closely.

I show a series of code snippets and corresponding output from running these snippets in this post to compare the standard JDK "unmodifiable" collections to Guava's "immutable" collections. Later in the post, I include the entire class's source code that features all these methods, but I show them a couple of methods at a time first so that I can focus on each.

Unmodifiable and Immutable Sets

The next code listing shows two methods, one that uses JDK's Collections.unmodifiableSet(Set) and one that uses Guava's ImmutableSet.copyOf(Collection). I show those two methods and the portion of the main function that calls those two methods. I don't show the methods that build up the sets of data provided to these instantiation methods, but those will be available later in this post in the code listing that contains the entire test class.

Demonstrating ImmutableSet.copyOf(Collection) and Collections.unmodifiableSet(Set)

/**
* Demonstrate Guava's ImmutableSet.
*/
public void demoGuavaImmutableSet()
{
printHeader("Guava's ImmutableSet");
final Set<String> originalStrings = buildUnderlyingSampleSet();
final ImmutableSet<String> strings = ImmutableSet.copyOf(originalStrings);
out.println("Immutable Set of Strings: " + strings);
originalStrings.remove("Java");
out.println("Original Set of Strings: " + originalStrings);
out.println("Immutable Set of Strings: " + strings);
}

/**
* Demonstrate JDK's UnmodifiableSet.
*/
public void demoJdkUnmodifiableSet()
{
printHeader("JDK unmodifiableSet");
final Set<String> originalStrings = buildUnderlyingSampleSet();
final Set<String> strings = Collections.unmodifiableSet(originalStrings);
out.println("Unmodifiable Set of Strings: " + strings);
originalStrings.remove("Java");
out.println("Original Set of Strings: " + originalStrings);
out.println("Unmodifiable Set of Strings: " + strings);
}

/**
* Main function to demonstrate Guava's immutable collections support.
*
* @param arguments Command-line arguments; none expected.
*/
public static void main(final String[] arguments)
{
final GuavaImmutableCollections me = new GuavaImmutableCollections();

// Compare JDK UnmodifiableSet to Guava's ImmutableSet
me.demoJdkUnmodifiableSet();
me.demoGuavaImmutableSet();
}

When the above code is executed, the output (seen in following screen snapshot) indicates that removal of the String "Java" from the original Set removes it also from the JDK "unmodifiable" Set that was created based on that original Set. The Guava "immutable Set, however, retains the "Java" String even when the same underlying Set upon which it was created has the "Java" String removed. We'll see that this is the case of the other Immutable collections as well.

Unmodifiable and Immutable Lists

As was the case for the Sets, I focus in the next code listing on the difference between using JDK's Collections.unmodifiableList(List) and Guava's ImmutableList.copyOf(Collection).

Demonstrating ImmutableList.copyOf(Collection) and Collections.unmodifiableList(List)

/**
* Demonstrate Guava's ImmutableList.
*/
public void demoGuavaImmutableList()
{
printHeader("Guava's ImmutableList");
final List<String> originalStrings = buildUnderlyingSampleList();
final ImmutableList<String> strings = ImmutableList.copyOf(originalStrings);
out.println("Immutable List of Strings: " + strings);
originalStrings.remove("Groovy");
out.println("Original List of Strings: " + originalStrings);
out.println("Immutable List of Strings: " + strings);
}

/**
* Demonstrate JDK's UnmodifiableList.
*/
public void demoJdkUnmodifiableList()
{
printHeader("JDK unmodifiableList");
final List<String> originalStrings = buildUnderlyingSampleList();
final List<String> strings = Collections.unmodifiableList(originalStrings);
out.println("Unmodifiable List of Strings: " + strings);
originalStrings.remove("Groovy");
out.println("Original List of Strings: " + originalStrings);
out.println("Unmodifiable List of Strings: " + strings);
}

/**
* Main function to demonstrate Guava's immutable collections support.
*
* @param arguments Command-line arguments; none expected.
*/
public static void main(final String[] arguments)
{
final GuavaImmutableCollections me = new GuavaImmutableCollections();

// Compare JDK UnmodifiableList to Guava's ImmutableList
me.demoJdkUnmodifiableList();
me.demoGuavaImmutableList();

The output that is shown in the next screen snapshot indicates that when the code above is executed, Guava's "immutable" List does not have its element removed even when the original collection does.

Unmodifiable and Immutable Maps

Although Guava's ImmutableMap features a copyOf(Map) method similar to those previously shown for Guava's ImmutableSet and ImmutableList, I choose to use a different approach to instantiating ImmutableMap when comparing it to that returned from the JDK's Collections.unmodifiableMap(Map) method. In this case, I use a "Builder" to build the Guava ImmutableMap. The result is the same: Guava's "immutable" Map does not have its values changed even when the underlying data structure from which the Guava immutable Map was populated is changed.

Demonstrating ImmutableMap.builder().putAll(Map) and Collections.unmodifiableMap(Map)

/**
* Demonstrate Guava's ImmutableMap. Uses ImmutableMap.builder().
*/
public void demoGuavaImmutableMap()
{
printHeader("Guava's ImmutableMap");
final Map<String, String> originalStringsMapping = new HashMap();
originalStringsMapping.put("D", "Dustin");
originalStringsMapping.put("G", "Guava");
originalStringsMapping.put("J", "Java");
final ImmutableMap<String, String> strings =
ImmutableMap.<String, String>builder().putAll(originalStringsMapping).build();
out.println("Immutable Map of Strings: " + strings);
originalStringsMapping.remove("D");
out.println("Original Map of Strings: " + originalStringsMapping);
out.println("Immutable Map of Strings: " + strings);
}

/**
* Demonstrate JDK's UnmodifiableMap.
*/
public void demoJdkUnmodifiableMap()
{
printHeader("JDK unmodifiableMap");
final Map<String, String> originalStringsMapping = new HashMap();
originalStringsMapping.put("D", "Dustin");
originalStringsMapping.put("G", "Guava");
originalStringsMapping.put("J", "Java");
final Map<String, String> strings = Collections.unmodifiableMap(originalStringsMapping);
out.println("Unmodifiable Map of Strings: " + strings);
originalStringsMapping.remove("D");
out.println("Original Map of Strings: " + originalStringsMapping);
out.println("Unmodifiable Map of Strings: " + strings);
}

/**
* Main function to demonstrate Guava's immutable collections support.
*
* @param arguments Command-line arguments; none expected.
*/
public static void main(final String[] arguments)
{
final GuavaImmutableCollections me = new GuavaImmutableCollections();

// Compare JDK unmodifiableMap to Guava's ImmutableMap
me.demoJdkUnmodifiableMap();
me.demoGuavaImmutableMap();
}

As was the case for Sets and Lists, the code above, when executed, leads to output (screen snapshot below) that shows that while the JDK's "unmodifiable" map is indeed modified if its underlying Map is modified (element removed), Guava's "immutable" Map does not lose an entry even when its original Map does.

Guava's Collections Builders

Although I only demonstrated use of a builder in creating my Guava immutable map example, Guava's immutable collections all support builders. This is shown for some of these collections in the next code listing.

Guava's Collections Builders

/**
* Demonstrate using Builders to build up Guava immutable collections.
*/
public void demoGuavaBuilders()
{
printHeader("Guava's Builders");

final ImmutableMap<String, String> languageStrings =
ImmutableMap.<String, String>builder().put("C", "C++")
.put("F", "Fortran")
.put("G", "Groovy")
.put("J", "Java")
.put("P", "Pascal")
.put("R", "Ruby")
.put("S", "Scala").build();
out.println("Languages Map: " + languageStrings);

final ImmutableSet<String> states =
ImmutableSet.<String>builder().add("Arizona")
.add("Colorado")
.add("Wyoming").build();
out.println("States: " + states);

final ImmutableList<String> cities =
ImmutableList.<String>builder().add("Boston")
.add("Colorado Springs")
.add("Denver")
.add("Fort Collins")
.add("Salt Lake City")
.add("San Francisco")
.add("Toledo").build();
out.println("Cities: " + cities);

final ImmutableMultimap<String, String> multimapLanguages =
ImmutableMultimap.<String, String>builder().put("C", "C")
.put("C", "C++")
.put("C", "C#")
.put("F", "Fortran")
.put("G", "Groovy")
.put("J", "Java")
.put("P", "Pascal")
.put("P", "Perl")
.put("P", "PHP")
.put("P", "Python")
.put("R", "Ruby")
.put("S", "Scala").build();
out.println("Languages: " + multimapLanguages);
}

/**
* Main function to demonstrate Guava's immutable collections support.
*
* @param arguments Command-line arguments; none expected.
*/
public static void main(final String[] arguments)
{
final GuavaImmutableCollections me = new GuavaImmutableCollections();

// Demonstrate using builders to build up Guava Immutable Collections
me.demoGuavaBuilders();
}
Other Guava Immutable Collections

Guava features immutable collections beyond those shown in this post (ImmutableMap, ImmutableSet, ImmutableList, and ImmutableMultimap). These others include ImmutableListMultimap, ImmutableMultiset, ImmutableBiMap, ImmutableSetMultimap, ImmutableSortedMap, and ImmutableSortedSet.

Elements Immutability or Mutability is Not Determined by the Collection Type

Guava's immutable collections are often preferable to the JDK's "unmodifiable" collections because the Guava immutable collections cannot be changed even when the data structure upon which they were first created changes. However, neither the JDK unmodifiable collections nor the Guava immutable collections can do anything about elements of the respective collections that are themselves mutable. In other words, a person may not be able to add an element or remove an element from an immutable or unmodifiable collection, but that same person can change any given element's contents if that element is mutable. In my examples above, I used String elements. Because Strings are, by their very nature, immutable, my collection elements cannot be changed. However, had I used classes that provide set methods or other ways to modify the given object, then these objects could have their value changed regardless of whether they are stored in immutable or unmodifiable collections.

The Entire Code Listing

The snippets of code shown above, along with some helper methods that they called but were not shown above, are available in the next code listing.

GuavaImmutableCollections.java

package dustin.examples;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import static java.lang.System.out;

import com.google.common.collect.ImmutableSet;
import java.util.*;

/**
* Class that demonstrates Guava's support of immutable collections.
*
* @author Dustin
*/
public class GuavaImmutableCollections
{
/**
* Build a sample set to be used in demonstrations.
*
* @return Sample set of Strings.
*/
public Set<String> buildUnderlyingSampleSet()
{
final Set<String> strings = new HashSet<String>();
strings.add("Dustin");
strings.add("Java");
strings.add("College Football");
return strings;
}

/**
* Build a sample list to be used in demonstrations.
*
* @return Sample list of Strings.
*/
public List<String> buildUnderlyingSampleList()
{
final List<String> gStrings = new ArrayList<String>();
gStrings.add("Guava");
gStrings.add("Groovy");
gStrings.add("Grails");
gStrings.add("Gradle");
gStrings.add("Grape");
return gStrings;
}

/**
* Demonstrate Guava's ImmutableSet.
*/
public void demoGuavaImmutableSet()
{
printHeader("Guava's ImmutableSet");
final Set<String> originalStrings = buildUnderlyingSampleSet();
final ImmutableSet<String> strings = ImmutableSet.copyOf(originalStrings);
out.println("Immutable Set of Strings: " + strings);
originalStrings.remove("Java");
out.println("Original Set of Strings: " + originalStrings);
out.println("Immutable Set of Strings: " + strings);
}

/**
* Demonstrate JDK's UnmodifiableSet.
*/
public void demoJdkUnmodifiableSet()
{
printHeader("JDK unmodifiableSet");
final Set<String> originalStrings = buildUnderlyingSampleSet();
final Set<String> strings = Collections.unmodifiableSet(originalStrings);
out.println("Unmodifiable Set of Strings: " + strings);
originalStrings.remove("Java");
out.println("Original Set of Strings: " + originalStrings);
out.println("Unmodifiable Set of Strings: " + strings);
}

/**
* Demonstrate Guava's ImmutableList.
*/
public void demoGuavaImmutableList()
{
printHeader("Guava's ImmutableList");
final List<String> originalStrings = buildUnderlyingSampleList();
final ImmutableList<String> strings = ImmutableList.copyOf(originalStrings);
out.println("Immutable List of Strings: " + strings);
originalStrings.remove("Groovy");
out.println("Original List of Strings: " + originalStrings);
out.println("Immutable List of Strings: " + strings);
}

/**
* Demonstrate JDK's UnmodifiableList.
*/
public void demoJdkUnmodifiableList()
{
printHeader("JDK unmodifiableList");
final List<String> originalStrings = buildUnderlyingSampleList();
final List<String> strings = Collections.unmodifiableList(originalStrings);
out.println("Unmodifiable List of Strings: " + strings);
originalStrings.remove("Groovy");
out.println("Original List of Strings: " + originalStrings);
out.println("Unmodifiable List of Strings: " + strings);
}

/**
* Demonstrate Guava's ImmutableMap. Uses ImmutableMap.builder().
*/
public void demoGuavaImmutableMap()
{
printHeader("Guava's ImmutableMap");
final Map<String, String> originalStringsMapping = new HashMap<String, String>();
originalStringsMapping.put("D", "Dustin");
originalStringsMapping.put("G", "Guava");
originalStringsMapping.put("J", "Java");
final ImmutableMap<String, String> strings =
ImmutableMap.<String, String>builder().putAll(originalStringsMapping).build();
out.println("Immutable Map of Strings: " + strings);
originalStringsMapping.remove("D");
out.println("Original Map of Strings: " + originalStringsMapping);
out.println("Immutable Map of Strings: " + strings);
}

/**
* Demonstrate JDK's UnmodifiableMap.
*/
public void demoJdkUnmodifiableMap()
{
printHeader("JDK unmodifiableMap");
final Map<String, String> originalStringsMapping = new HashMap<String, String>();
originalStringsMapping.put("D", "Dustin");
originalStringsMapping.put("G", "Guava");
originalStringsMapping.put("J", "Java");
final Map<String, String> strings = Collections.unmodifiableMap(originalStringsMapping);
out.println("Unmodifiable Map of Strings: " + strings);
originalStringsMapping.remove("D");
out.println("Original Map of Strings: " + originalStringsMapping);
out.println("Unmodifiable Map of Strings: " + strings);
}

/**
* Demonstrate using Builders to build up Guava immutable collections.
*/
public void demoGuavaBuilders()
{
printHeader("Guava's Builders");

final ImmutableMap<String, String> languageStrings =
ImmutableMap.<String, String>builder().put("C", "C++")
.put("F", "Fortran")
.put("G", "Groovy")
.put("J", "Java")
.put("P", "Pascal")
.put("R", "Ruby")
.put("S", "Scala").build();
out.println("Languages Map: " + languageStrings);

final ImmutableSet<String> states =
ImmutableSet.<String>builder().add("Arizona")
.add("Colorado")
.add("Wyoming").build();
out.println("States: " + states);

final ImmutableList<String> cities =
ImmutableList.<String>builder().add("Boston")
.add("Colorado Springs")
.add("Denver")
.add("Fort Collins")
.add("Salt Lake City")
.add("San Francisco")
.add("Toledo").build();
out.println("Cities: " + cities);

final ImmutableMultimap<String, String> multimapLanguages =
ImmutableMultimap.<String, String>builder().put("C", "C")
.put("C", "C++")
.put("C", "C#")
.put("F", "Fortran")
.put("G", "Groovy")
.put("J", "Java")
.put("P", "Pascal")
.put("P", "Perl")
.put("P", "PHP")
.put("P", "Python")
.put("R", "Ruby")
.put("S", "Scala").build();
out.println("Languages: " + multimapLanguages);
}

/**
* Write a separation header to standard output that includes provided header
* text.
*
* @param headerText Text to be used in separation header.
*/
public static void printHeader(final String headerText)
{
out.println("\n========================================================");
out.println("== " + headerText);
out.println("========================================================");
}

/**
* Main function to demonstrate Guava's immutable collections support.
*
* @param arguments Command-line arguments; none expected.
*/
public static void main(final String[] arguments)
{
final GuavaImmutableCollections me = new GuavaImmutableCollections();

// Compare JDK UnmodifiableSet to Guava's ImmutableSet
me.demoJdkUnmodifiableSet();
me.demoGuavaImmutableSet();

// Compare JDK UnmodifiableList to Guava's ImmutableList
me.demoJdkUnmodifiableList();
me.demoGuavaImmutableList();

// Compare JDK unmodifiableMap to Guava's ImmutableMap
me.demoJdkUnmodifiableMap();
me.demoGuavaImmutableMap();

// Demonstrate using builders to build up Guava Immutable Collections
me.demoGuavaBuilders();
}
}
Conclusion

The Guava "immutable" collections are often preferable to the similar JDK "unmodifiable" collections provided by the Collections class because Guava's immutable collections cannot be changed even when the original data structure upon which they were created is changed. The reason for the difference is that the JDK's "unmodifiable" collections are "views" of underlying collections and these views are changed if the thing they are "viewing" is changed. The Guava immutable collections, on the other hand, are not mere views of the source data structure, but are copies of it such that changes to the original structure have no impact on the copied immutable collection.