Monday, October 31, 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.

Sunday, October 30, 2011

Dennis Ritchie Day

In the post Dennis Ritchie Day, Tim O’Reilly "declares this Sunday, October 30 to be Dennis Ritchie Day!" O'Reilly acknowledges that "I don't have the convening power of a Governor Brown" (who declared Sunday, October 16, as Steve Jobs Day), but calls for Dennis Ritchie Day to be observed by "those of us around the world who care." O'Reilly uses this post to enumerate many of the advances that Ritchie has had a hand in throughout the development of modern day processor-based systems. Related posts on this subject include Denver Post's Quillen: Computer superstar's death overlooked, The most important man in tech died last week…It wasn’t Steve Jobs., and Dennis Ritchie: The Shoulders Steve Jobs Stood On.

In Honor of Dennis Ritchie Day is a page for today only (offer expires on Halloween, 31 October 2011), O'Reilly is offering: "Save 50% on C/C++, Linux, and Unix Ebooks & Videos." The page also reminds us that "Ebooks from oreilly.com are DRM-free. You get free lifetime access, multiple file formats, free updates." I'm thinking about getting Unix Power Tools, Third Edition.

Not everyone is happy about books being sold by O'Reilly in honor of Dennis Ritchie Day. However, one poster points out, "If this prompts more people to discover Ritchie's work then I don't necessarily think it's a bad thing."

October 2011 has been a bad month for losing pioneers in our industry. Besides the loss of Steve Jobs (October 5) and Dennis Ritchie (October 12), we also lost John McCarthy in October (October 23). calibraxis suggests we can honor McCarthy by purchasing books on LISP-inspired technologies such as Clojure and Erlang. John Fitzpatrick also points out that we also lost Daniel McCracken this year (July 30) as well.

Saturday, October 29, 2011

Guava Stopwatch

Guava's Stopwatch is another Guava class new to Guava Release 10 (as is Optional, the subject of another recent post). As its name implies, this simple class provides a method to conveniently measure time elapsed between two code points. It has several advantages over use of System.currentTimeMillis() or System.nanoTime(). I don't focus on these advantages here, but the Javadoc documentation for Stopwatch does cover some of these advantages.

As is true of many of Guava's classes, one of the endearing features of Stopwatch is its simplicity and appropriately named methods. The class features two constructors, one that takes no arguments (likely to be most commonly used) and one that accepts an customized extension of the Ticker class. Once an instance of Stopwatch is obtained, it's a simple matter of using methods with "obvious" names like start(), stop(), and reset() to control the stopwatch.

Any given Stopwatch instance records elapsed time in a cumulative fashion. In other words, you can start and stop the stopwatch multiple times (just don't start an already started stopwatch and don't stop an already stopped stopwatch) and the elapsed time accumulates with each start and stop. If that's not what is wanted and a single instance of the stopwatch is to be used to measure independent events (but not concurrent events), then the reset() method is used between the last run's stop() and the next run's start().

I have already alluded to several caveats to keep in mind when using Guava's Stopwatch. First, two successive start() methods should not be invoked on a given instance of Stopwatch without first stopping it with stop() before making the second call to stop(). Stopwatch has an instance method isRunning() that is useful for detecting a running stopwatch before trying to start it again or even before trying to stop one that has already been stopped or was never started. Most of these issues such as starting the stopwatch twice without stopping it or stopping a stopwatch that is not running or was never started lead to IllegalStateExceptions being thrown. Guava developers make use of their own Preconditions class to ascertain these aberrant conditions and to the throwing of these exceptions. The further implication of this, which is spelled out in the Javadoc documentation, is that Stopwatch is not thread-safe and should be used in a single-thread environment.

The methods covered so far handle constructing instances of Stopwatch and managing the stopwatch. However, a stopwatch is almost always useful only when the timed results are available for viewing. The Stopwatch class provides two main methods for accessing elapsed time recorded by the stopwatch instance. One method, elapsedMillis(), is similar to standard Java methods that return milliseconds since epoch time. The big difference here is that Stopwatch is returning milliseconds elapsed between given points in time (start() and stop() calls) versus since an absolute epoch time.

I prefer elapsedTime(TimeUnit) for acquiring the elapsed time recorded in my stopwatch instance. This method makes use of the TimeUnit enum (see my post on TimeUnit) to specify the units that the elapsed time should be expressed in. Both of these methods for reporting elapsed time can be run while the stopwatch is running or after it has stopped.

The following code listing contains a class that demonstrates the methods on Stopwatch that have been highlighted in this post.

StopWatchDemo.java
package dustin.examples;

import static java.lang.System.out;

import com.google.common.base.Stopwatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Demonstrates Guava's (Release 10) Stopwatch class.
 * 
 * @author Dustin
 */
public class StopWatchDemo
{
   private final static Logger LOGGER = Logger.getLogger(StopWatchDemo.class.getCanonicalName());

   public static void doSomethingJustToBeDoingIt(final int numberOfTimesToDoNothing)
   {
      for (int count=0; count < numberOfTimesToDoNothing; count++)
      {
         try
         {
            Thread.sleep(TimeUnit.SECONDS.toMillis(1));
         }
         catch (InterruptedException interruptEx)
         {
            LOGGER.log(Level.INFO, "Don't interrupt me when I'm trying to sleep!", interruptEx);
         }
      }
   }

   /**
    * Print statistics on Stopwatch-reported times for provided number of loops.
    * 
    * @param numberLoops Number of loops executed.
    * @param stopwatch Stopwatch instance with time used statistics.
    */
   public static void printElapsedTime(final int numberLoops, final Stopwatch stopwatch)
   {
      if (stopwatch.isRunning())
      {
         out.println("WARNING! Your stopwatch is still running!");
      }
      else // stopwatch not running
      {
         out.println(numberLoops + " loops required: ");
         out.println("\t" + stopwatch.toString(6));
         out.println("\t" + stopwatch.elapsedMillis() + " elapsed milliseconds.");
         out.println("\t" + stopwatch.elapsedTime(TimeUnit.MINUTES) + " minutes");
         out.println("\t" + stopwatch.elapsedTime(TimeUnit.SECONDS) + " seconds");
         out.println("\t" + stopwatch.elapsedTime(TimeUnit.MILLISECONDS) + " milliseconds");
         out.println("\t" + stopwatch.elapsedTime(TimeUnit.NANOSECONDS) + " nanoseconds");
      }
   }

   public static void main(final String[] arguments)
   {
      final Stopwatch stopwatch = new Stopwatch();

      int numberTimes = 5;
      stopwatch.start();
      doSomethingJustToBeDoingIt(numberTimes);
      stopwatch.stop();
      printElapsedTime(numberTimes, stopwatch);

      numberTimes = 45;
      stopwatch.reset();
      stopwatch.start();
      doSomethingJustToBeDoingIt(numberTimes);
      stopwatch.stop();
      printElapsedTime(numberTimes, stopwatch);

      numberTimes = 125;
      stopwatch.reset();
      stopwatch.start();
      doSomethingJustToBeDoingIt(numberTimes);
      stopwatch.stop();
      printElapsedTime(numberTimes, stopwatch);
   }
}

When the above code is executed, its output is similar to that shown in the following screen snapshot.

If I comment out the lines that reset the stop watch instance, the stopwatch instance accumulates elapsed time rather than tracking it separately. This difference is shown in the next screen snapshot.

The Guava Stopwatch class makes it easy to perform simple timing measurements to analyze how long certain operations take. It is easy to use and provides the flexibility to readily provide output in the desired time scale.

Friday, October 28, 2011

Guava's New Optional Class

Guava Release 10 introduces the Optional class, which can be used where one might use a null object. I have built my own classes like this before, but the advantage of Guava's providing it is that it can be easily reused across projects and developers new to an organization might know about it already if they use Guava. In addition, use of the Guava class is advantageous when compared to use of one's own similar or null object class because of the normal advantages associated with open source communities such as more testing and more eyes on the code.

As I wrote about in the post Effective Java NullPointerException Handling, I generally don't like to have methods return null. With methods that return collections, returning an empty collection is an easy solution. When methods return arbitrary objects, however, it is not always easy to indicate an "empty" condition. Guava's Optional class can fill this need nicely.

The Optional class has no constructors, but provides three public static methods for acquiring an instance of the class. Optional.fromNullable(T) allows a null or non-null reference to be provided and wrapped in the new Optional instance. If the passed-in parameter is null, then the instance does not have any reference stored and is an "absent" instance. If the passed-in parameter is not null, then that non-null reference is stored within the new Optional instance.

Another publicly available static method for getting an instance of Optional is Optional.of(T). This method acts like Optional.fromNullable(T) except that it expects a non-null parameter to be passed to it. If null is passed to it, a NullPointerException is thrown.

The third static method for acquiring an instance of Optional is Optional.absent(). This is useful when one has code that knows the parameter that would have been provided to Optional.fromNullable(T) is null and it is more clear to express than an "absent" version of Optional should be returned.

Once an instance of Optional has been acquired (such as returned by a method), there are several instance methods that can be called on that instance. The Optional.isPresent() method is useful for determining if a given Optional instance has a non-null parameter within it.

Once it is known (such as by calling Optional.isPresent()) that an Optional instance contains a non-null reference, the Optional.get() method returns that stored non-null reference. Note that if there is no non-null reference, an exception is thrown upon this method's invocation, making it a good idea to call isPresent() first.

There are several overloaded or methods that allow defaults to be specified that are returned when the Optional instance does not contain its own non-null reference. These methods provide a nice way to specify a default value to be returned when it would be null returned otherwise (or an exception thrown). There may be times, especially when interacting with pre-existing APIs, that it is desirable to return a null rather than an "absent" Optional instance. This is easily accomplished through use of Optional.orNull() which returns either the reference type the instance contains or else returns null if it doesn't contain a non-null reference.

Many of the concepts discussed above are exemplified in the following code listing.

GuavaOptional.java
package dustin.examples;

import static java.lang.System.out;

import com.google.common.base.Optional;
import com.google.common.collect.Maps;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Demonstrate use of Guava's Optional class.
 * 
 * @author Dustin
 */
public class GuavaOptional
{
   /** java.util.logging Logger handle. */
   private final static Logger LOGGER = Logger.getLogger(GuavaOptional.class.getCanonicalName());

   /** Map of state names to the names of that state's capital. */
   private final static Map<String, String> stateCapitals;

   static
   {
      final Map<String, String> tempStatesToCapitals = Maps.newHashMap();
      tempStatesToCapitals.put("Alaska", "Juneau");
      tempStatesToCapitals.put("Arkansas", "Little Rock");
      tempStatesToCapitals.put("Colorado", "Denver");
      tempStatesToCapitals.put("Idaho", "Boise");
      tempStatesToCapitals.put("Utah", "Salt Lake City");
      tempStatesToCapitals.put("Wyoming", "Cheyenne");
      stateCapitals = Collections.unmodifiableMap(tempStatesToCapitals);
   }

   /**
    * Provide the name of the capital of the provided state. This method uses
    * Guava's Optional.fromNullable(T) to ensure that a non-null Optional instance
    * is always returned with a non-null contained reference or without a
    * contained reference.
    * 
    * @param stateName State whose capital is desired.
    * @return Instance of Optional possibly containing the capital corresponding
    *    to provided the state name, if available.
    */
   public Optional<String> getStateCapital(final String stateName)
   {
      return Optional.fromNullable(stateCapitals.get(stateName));
   }

   /**
    * Provide quotient resulting from dividing dividend by divisor.
    * 
    * @param dividend Dividend used in division.
    * @param divisor Divisor used in division.
    * @return Optional wrapper potentially containing Quotient from dividing
    *    dividend by divisor.
    */
   public Optional<BigDecimal> getQuotient(final BigDecimal dividend, final BigDecimal divisor)
   {
      BigDecimal quotient;
      try
      {
         quotient = dividend.divide(divisor);
      }
      catch (Exception ex)
      {
         LOGGER.log(Level.SEVERE, "Unable to divide " + dividend + " by " + divisor + "-", ex);
         quotient = null;
      }
      return Optional.fromNullable(quotient);
   }

   /**
    * Main function for demonstrating Guava's optional class.
    * 
    * @param arguments Command-line arguments; none expected.
    */
   public static void main(final String[] arguments)
   {
      final GuavaOptional me = new GuavaOptional();

      final String wyoming = "Wyoming";
      final Optional<String> wyomingCapitalWrapper = me.getStateCapital(wyoming);
      if (wyomingCapitalWrapper.isPresent())
      {
         out.println("Capital of " + wyoming + " is " + wyomingCapitalWrapper.get());
      }
      out.println("Capital of " + wyoming + " is " + wyomingCapitalWrapper.orNull());

      final String northDakota = "North Dakota";
      final Optional<String> northDakotaCapitalWrapper = me.getStateCapital(northDakota);
      out.println("Capital of " + northDakota + " is " + northDakotaCapitalWrapper);
      out.println("Capital of " + northDakota + " is " + northDakotaCapitalWrapper.or("Unspecified"));
      out.println("Capital of " + northDakota + " is " + northDakotaCapitalWrapper.orNull());

      final Optional<String> nullOptional = me.getStateCapital(null);
      out.println("Capital of null is " + nullOptional);
      

      final BigDecimal dividend = new BigDecimal("5.0");
      final BigDecimal divisor = new BigDecimal("0.0");
      final Optional<BigDecimal> quotientWrapper = me.getQuotient(dividend, divisor);
      out.println(  "Quotient of " + dividend + " / " + divisor + " is "
                  + quotientWrapper);
   }
}

Proper use of Guava's Optional class can reduce NullPointerExceptions (NPEs) and increase the expressiveness of return values. However, even using Optional does not mean the end of exceptions. For example, passing a null as the parameter to the static method Optional.of(T) when attempting to obtain an instance of Optional leads to an NPE. This is not too surprising given that the method's documentation states, "Returns an Optional instance containing the given non-null reference." However, the Javadoc for the method does not explicitly state that this is thrown. It is interesting that this condition is detected using Guava's own Preconditions class. Another exception that can occur is IllegalStateException when the Optional.get() method is called on an instance of Optional that does not have a non-null reference contained within it. This method's Javadoc documentation does state conditions under which this one can be thrown.

Guava seems to specialize in providing common functionality and utilities many of us develop for our own code bases. The advantages of using Guava's classes include being able to use classes that Java developers outside a given organization have a chance to know about as well as the advantages of using products supported by open source communities. Guava's Optional provides a convenient and easy-to-use mechanism for adding safety and expressiveness to a code base. There are still situations where I prefer a more specific type of null object (the object represents either null or a particular non-null type), but Optional is nice for those situations where I need a general solution because I cannot justify the cost of creating a specific solution.

Guava Presents Java Throwable Stack Trace as Single String

Tip #5 of my blog post Ten Tips for Using Java Stack Traces demonstrates two example of using Java code to extract a Throwable's (implying Error or Exception) stack trace into a single String representation using either PrintWriter or PrintStream. These are not difficult to implement, but must be implemented for every code base in which it is desirable to get a String representing the entire stack trace. For a program that is already using Guava for other niceties it provides, a nice alternative is to use Guava's Throwables class and to specifically use its aptly named getStackTraceAsString(Throwable) method to extract a String from a provided Throwable.

The Javadoc documentation describes how simple it is to use this method:

Returns a string containing the result of toString(), followed by the full, recursive stack trace of throwable. Note that you probably should not be parsing the resulting string; if you need programmatic access to the stack frames, you can call Throwable.getStackTrace().

The Javadoc points out that this String should not be used for programmatic access of the stack trace. Instead, StackTraceElements should be accessed and manipulated from Throwable.getStackTrace() as I discussed in The Surprisingly Simple StackTraceElement.

Guava's Throwables class has other useful methods for dealing with Java's Exceptions and Errors as well. There are multiple methods for propagating Throwables based on certain conditions and there are methods for more precise control of chained exceptions. For example, Throwables.getRootCause(Throwable) returns the exception that is often most important (see Tips #2 and #3 in Ten Tips for Using Java Stack Traces) in a chain (the "innermost" or "first throwable" in the chain) from a provided Throwable. Another example is the method Throwables.getCausalChain() that presents the chained Throwables in a Java List.

I probably would not include Guava in a project solely to extract Throwable stack traces into Strings because I could write my own static method with a few lines of code to do the same thing and put that in a utilities or common package in my code base. However, if I'm using Guava anyway for other reasons and including it in my dependencies, then it is preferable to use the Guava-provided functionality rather than rolling out my own that others need to be made aware of and learn about.

Tuesday, October 25, 2011

Guava Preconditions Class

Anyone who's written much Java has probably written methods that begin with conditionals that verify either the provided parameters or the state of the object being acted upon before proceeding with the remainder of the method's implementation. These can add verbosity to the method and sometimes, especially if there are multiple checks, can almost drown out the interesting business logic of the method. One way to reduce this clutter is to use Java assertions, but these are disabled at runtime by default. In fact, it is advised to "not use assertions for argument checking in public methods" for this reason and to use appropriate runtime exceptions instead. Guava provides a handy Preconditions class with aesthetic advantages of assertions, but which uses the normal Java exception mechanism and is not disabled at runtime. This post provides some illustrative examples of Guava's Preconditions class in action.

The next code listing shows contrived examples using Guava's Preconditions class. The example demonstrate use of static methods on the Preconditions class to check a parameter for null, check a parameter's placement in a provided array, check to ensure a valid value has been provided for a parameter, and to check that the state of the object on which the method is invoked is appropriate for that method's execution. Note also that I made use of the static import for the Preconditions class so that I am able to call its static methods without need to scope each call with the class name.

GuavaPreconditionsDemo.java
package dustin.examples;

import static java.lang.System.err;
import static com.google.common.base.Preconditions.*;

/**
 * Simple demonstration of Guava's Preconditions support.
 * 
 * @author Dustin
 */
public class GuavaPreconditionsDemo
{
   private final boolean initialized = false;

   /**
    * Demonstrate Guava's Preconditions.checkNotNull methods.
    * 
    * @param parameter Parameter that is checked for null-ness.
    */
   public void testForNonNullArgument(final String parameter)
   {
      final String localParameter = checkNotNull(parameter, "Provided parameter is unacceptably null.");
   }

   public void testDivisorNotZero(final int divisor)
   {
      checkArgument(divisor != 0, "Zero divisor not allowed.");
   }

   public void testArrayElement(final String[] strArray, final int indexNumber)
   {
      final int index = checkElementIndex(indexNumber, strArray.length, "String array index number");
   }

   public void testArrayPosition(final String[] strArray, final int indexNumber)
   {
      final int index = checkPositionIndex(indexNumber, strArray.length, "String array index number");
   }

   public void testState()
   {
      checkState(this.initialized, "Cannot perform action because not initialized.");
   }

   public static void printHeader(final String newHeaderText)
   {
      err.println("\n==========================================================");
      err.println("== " + newHeaderText);
      err.println("==========================================================");      
   }

   /**
    * Main function for executing demonstrations of Guava's Preconditions.
    */
   public static void main(final String[] arguments)
   {
      final GuavaPreconditionsDemo me = new GuavaPreconditionsDemo();

      printHeader("Preconditions.checkNotNull");
      try
      {
         me.testForNonNullArgument(null);
      }
      catch (NullPointerException npe)
      {
         npe.printStackTrace();
      }

      printHeader("Preconditions.checkArgument");
      try
      {
         me.testDivisorNotZero(0);
      }
      catch (IllegalArgumentException illArgEx)
      {
         illArgEx.printStackTrace();
      }

      printHeader("Preconditions.checkElementIndex");
      try
      {
         me.testArrayElement(new String[]{"Dustin", "Java"}, 3);
      }
      catch (IndexOutOfBoundsException ioobEx)
      {
         ioobEx.printStackTrace();
      }

      printHeader("Preconditions.checkPositionIndex");
      try
      {
         me.testArrayPosition(new String[]{"Dustin", "Java"}, 3);
      }
      catch (IndexOutOfBoundsException ioobEx)
      {
         ioobEx.printStackTrace();
      }

      printHeader("Preconditions.checkState");
      try
      {
         me.testState();
      }
      catch (IllegalStateException illStateEx)
      {
         illStateEx.printStackTrace();
      }
   }
}

Each of the cases demonstrated in the code listing above checked for preconditions on the method parameters or on the state of the object against which the method was being invoked without use of "noisy" conditional statements. Use of the static import allowed very concise expression of the conditions to be checked, Much of the class is the "main" function, used as a "test driver" in this case. I placed the calls within try-catch block to make sure all demonstrations are executed. The output of running the above is shown next.

Output From Executing Above Class
==========================================================
== Preconditions.checkNotNull
==========================================================
java.lang.NullPointerException: Provided parameter is unacceptably null.
 at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:204)
 at dustin.examples.GuavaPreconditionsDemo.testForNonNullArgument(GuavaPreconditionsDemo.java:22)
 at dustin.examples.GuavaPreconditionsDemo.main(GuavaPreconditionsDemo.java:62)

==========================================================
== Preconditions.checkArgument
==========================================================
java.lang.IllegalArgumentException: Zero divisor not allowed.
 at com.google.common.base.Preconditions.checkArgument(Preconditions.java:88)
 at dustin.examples.GuavaPreconditionsDemo.testDivisorNotZero(GuavaPreconditionsDemo.java:27)
 at dustin.examples.GuavaPreconditionsDemo.main(GuavaPreconditionsDemo.java:72)

==========================================================
== Preconditions.checkElementIndex
==========================================================
java.lang.IndexOutOfBoundsException: String array index number (3) must be less than size (2)
 at com.google.common.base.Preconditions.checkElementIndex(Preconditions.java:301)
 at dustin.examples.GuavaPreconditionsDemo.testArrayElement(GuavaPreconditionsDemo.java:32)
 at dustin.examples.GuavaPreconditionsDemo.main(GuavaPreconditionsDemo.java:82)

==========================================================
== Preconditions.checkPositionIndex
==========================================================
java.lang.IndexOutOfBoundsException: String array index number (3) must not be greater than size (2)
 at com.google.common.base.Preconditions.checkPositionIndex(Preconditions.java:351)
 at dustin.examples.GuavaPreconditionsDemo.testArrayPosition(GuavaPreconditionsDemo.java:37)
 at dustin.examples.GuavaPreconditionsDemo.main(GuavaPreconditionsDemo.java:92)

==========================================================
== Preconditions.checkState
==========================================================
java.lang.IllegalStateException: Cannot perform action because not initialized.
 at com.google.common.base.Preconditions.checkState(Preconditions.java:145)
 at dustin.examples.GuavaPreconditionsDemo.testState(GuavaPreconditionsDemo.java:42)
 at dustin.examples.GuavaPreconditionsDemo.main(GuavaPreconditionsDemo.java:102)

The different static Preconditions methods throw different types of runtime exceptions when a specified condition is violated. They throw exceptions that tend to be appropriate for the particular case that is violated. This means that in most cases the result of a Preconditions static method call is the same as one would likely throw explicitly for that condition, but with much less code to make the check and throw the exception. I did not show it in this post, but overloaded versions of these static methods also allow for string arguments to be provided to fill placeholders in a patterned String. This is helpful for placing values associated with the errant condition in the exception message.

Conclusion

Guava eases the coding and improves the fluency and readability of contract checking in methods. It offers the advantages of assertions' concise syntax coupled with the advantages of traditional throwing of runtime exceptions.

Between Two Points with Groovy

I needed to check a ninth grader's math homework related to calculating three things about a line between two points. The assignment was to calculate the distance between two points, the slope of a line connecting the two points, and the midpoint on a line between the two points. Because I was tired after a long day and because I inherently possess the long-advertised software developer "laziness," I decided to write up a quick Groovy script to calculate these numbers for me. There were enough problems to check that I believe I either broke even or even came out a little ahead in terms of time required to write the script versus plugging the numbers into a calculator. I include that script in this post for easy future access when needed. It also provides a chance to look at some Groovy advantages.

findInfoBetweenTwoPoints.groovy
#!/usr/bin/env groovy
// findInfoBetweenTwoPoints.groovy x1 y1 x2 y2

if (args.length < 4)
{
   println "You must enter four numerals: x1 y1 x2 y2"
   System.exit(-1)
}

def inputX1 = args[0]
if (!inputX1.isNumber())
{
   println "${inputX1} is NOT a numeral."
   System.exit(-2)
}
def inputY1 = args[1]
if (!inputY1.isNumber())
{
   println "${inputY1} is NOT a numeral."
   System.exit(-3)
}
def inputX2 = args[2]
if (!inputX2.isNumber())
{
   println "${inputX2} is NOT a numeral."
   System.exit(-4)
}
def inputY2 = args[3]
if (!inputY2.isNumber())
{
   println "${inputY2} is NOT a numeral."
   System.exit(-5)
}

def x1 = inputX1 as BigDecimal
def y1 = inputY1 as BigDecimal
def x2 = inputX2 as BigDecimal
def y2 = inputY2 as BigDecimal

def distanceSquared = Math.pow(y2-y1, 2.0) + Math.pow(x2-x1, 2.0)
def distance = Math.sqrt(distanceSquared)
def slope = x2 != x1 ? ((y2 - y1)/(x2 - x1)) : Double.POSITIVE_INFINITY
if (slope == 0)
{
   println "Horizontal line!"
}
else if (slope == Double.POSITIVE_INFINITY)
{
   println "Vertical line!"
}
def midPointX = (x1 + x2) / 2
def midPointY = (y1 + y2) / 2

println "For points (${x1}, ${y1}) and (${x2}, ${y2}):";
println "\tDistance Between points: sqrt(${distanceSquared}) = ${distance}"
println "\tSlope (m): ${slope}"
println "\tMidpoint: (${midPointX}, ${midPointY})"

This is a fairly basic Groovy script, but it does remind one of how nice it is to strip away the normal ceremony of having a class and methods and all the other overhead to leave a fairly straightforward logic-heavy script. Some nice Groovy-isms are demonstrated including use of the implicit args handle, use of the as keyword to fluently cast the String arguments into BigDecimal instances, use of Groovy's (GDK) String.isNumber(), and use of placeholders in Groovy Strings for greater readability. It is really easy to quickly identify the core of this script, which is the mathematical functions used to calculate midpoint, slope, and distance.

The following screen snapshot shows a couple of examples of running this script on different values.

Conclusion

It says something for ease of use of a scripting language when it's easier to code up a simple script for performing calculations than it is to use a calculator to perform the same calculations manually. Of course, I could have programmed a sophisticated calculator to do the same thing, but I think it was probably easier to code this script than to go find my calculator or learn how to use and program the ninth grader's calculator!

Monday, October 24, 2011

Guava's Objects Class: Equals, HashCode, and ToString

If you are fortunate enough to be using JDK 7, the newly available Objects class is the obvious (at least to me) choice for implementing the "common" Java object methods such as equals(Object) [with Objects.equals(Object,Object)], hashCode() [with Objects.hashCode(Object) or Objects.hash(Object...)], and toString() [with Objects.toString(Object)] to appropriately override the default Object implementations. I have written posts about using Objects class: JDK 7: The New Objects Class and Java 7 Objects-Powered Compact Equals.

If you're not yet using Java 7, your best choices might be the Apache Commons builders ToStringBuilder and EqualsBuilder and HashCodeBuilder (if you're using a version of Java prior to J2SE 5) or Guava (if you're using J2SE 5 or later). In this post, I look at using Guava's Objects class to implement the three common methods equals, hashCode, and toString().

Without Guava or other library to help, the three common methods discussed in this post are often highlighted as shown in the next code listing. These methods were generated with NetBeans 7.1 beta.

TraditionalEmployee
package dustin.examples;

import java.util.Calendar;

/**
 * Simple employee class using NetBeans-generated 'common' methods
 * implementations that are typical of many such implementations created
 * without Guava or other library.
 * 
 * @author Dustin
 */
public class TraditionalEmployee
{
   public enum Gender{ FEMALE, MALE };

   private final String lastName;
   private final String firstName;
   private final String employerName;
   private final Gender gender;

   /**
    * Create an instance of me.
    * 
    * @param newLastName The new last name my instance will have.
    * @param newFirstName The new first name my instance will have.
    * @param newEmployerName The employer name my instance will have.
    * @param newGender The gender of my instance.
    */
   public TraditionalEmployee(
      final String newLastName, final String newFirstName,
      final String newEmployerName, final Gender newGender)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
      this.employerName = newEmployerName;
      this.gender = newGender;
   }

   public String getEmployerName()
   {
      return this.employerName;
   }

   public String getFirstName()
   {
      return this.firstName;
   }

   public Gender getGender()
   {
      return this.gender;
   }

   public String getLastName()
   {
      return this.lastName;
   }

   /**
    * NetBeans-generated method that compares provided object to me for equality.
    * 
    * @param obj Object to be compared to me for equality.
    * @return {@code true} if provided object is considered equal to me or
    *    {@code false} if provided object is not considered equal to me.
    */
   @Override
   public boolean equals(Object obj)
   {
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }
      final TraditionalEmployee other = (TraditionalEmployee) obj;
      if ((this.lastName == null) ? (other.lastName != null) : !this.lastName.equals(other.lastName))
      {
         return false;
      }
      if ((this.firstName == null) ? (other.firstName != null) : !this.firstName.equals(other.firstName))
      {
         return false;
      }
      if ((this.employerName == null) ? (other.employerName != null) : !this.employerName.equals(other.employerName))
      {
         return false;
      }
      if (this.gender != other.gender)
      {
         return false;
      }
      return true;
   }

   /**
    * NetBeans-generated method that provides hash code of this employee instance.
    * 
    * @return My hash code.
    */
   @Override
   public int hashCode()
   {
      int hash = 3;
      hash = 19 * hash + (this.lastName != null ? this.lastName.hashCode() : 0);
      hash = 19 * hash + (this.firstName != null ? this.firstName.hashCode() : 0);
      hash = 19 * hash + (this.employerName != null ? this.employerName.hashCode() : 0);
      hash = 19 * hash + (this.gender != null ? this.gender.hashCode() : 0);
      return hash;
   }

   /**
    * NetBeans-generated method that provides String representation of employee
    * instance.
    * 
    * @return My String representation.
    */
   @Override
   public String toString()
   {
      return  "TraditionalEmployee{" + "lastName=" + lastName + ", firstName=" + firstName
            + ", employerName=" + employerName + ", gender=" + gender +  '}';
   }
}

Although NetBeans 7.1 beta did the heavy lifting here, this code still must be maintained and can be made more readable. The next class is the same class, but with Guava-powered common methods instead of the NetBeans-generated 'typical' implementations shown above.

GuavaEmployee
package dustin.examples;

/**
 * Simple employee class using Guava-powered 'common' methods implementations.
 * 
 * I explicitly scope the com.google.common.base.Objects class here to avoid the
 * inherent name collision with the java.util.Objects class.
 * 
 * @author Dustin
 */
public class GuavaEmployee
{
   public enum Gender{ FEMALE, MALE };

   private final String lastName;
   private final String firstName;
   private final String employerName;
   private final TraditionalEmployee.Gender gender;

   /**
    * Create an instance of me.
    * 
    * @param newLastName The new last name my instance will have.
    * @param newFirstName The new first name my instance will have.
    * @param newEmployerName The employer name my instance will have.
    * @param newGender The gender of my instance.
    */
   public GuavaEmployee(
      final String newLastName, final String newFirstName,
      final String newEmployerName, final TraditionalEmployee.Gender newGender)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
      this.employerName = newEmployerName;
      this.gender = newGender;
   }

   public String getEmployerName()
   {
      return this.employerName;
   }

   public String getFirstName()
   {
      return this.firstName;
   }

   public TraditionalEmployee.Gender getGender()
   {
      return this.gender;
   }

   public String getLastName()
   {
      return this.lastName;
   }

   /**
    * Using Guava to compare provided object to me for equality.
    * 
    * @param obj Object to be compared to me for equality.
    * @return {@code true} if provided object is considered equal to me or
    *    {@code false} if provided object is not considered equal to me.
    */
   @Override
   public boolean equals(Object obj)
   {
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }
      final GuavaEmployee other = (GuavaEmployee) obj;
      
      return   com.google.common.base.Objects.equal(this.lastName, other.lastName)
            && com.google.common.base.Objects.equal(this.firstName, other.firstName)
            && com.google.common.base.Objects.equal(this.employerName, other.employerName)
            && com.google.common.base.Objects.equal(this.gender, other.gender);
   }

   /**
    * Uses Guava to assist in providing hash code of this employee instance.
    * 
    * @return My hash code.
    */
   @Override
   public int hashCode()
   {
      return com.google.common.base.Objects.hashCode(
                this.lastName, this.firstName, this.employerName, this.gender);
   }

   /**
    * Method using Guava to provide String representation of this employee
    * instance.
    * 
    * @return My String representation.
    */
   @Override
   public String toString()
   {
      return com.google.common.base.Objects.toStringHelper(this)
                .addValue(this.lastName)
                .addValue(this.firstName)
                .addValue(this.employerName)
                .addValue(this.gender)
                .toString();
   }
}

As the code above proves, the use of Guava improves the readability of the implementations of the three common methods. The only thing that's not so nice is the need to explicitly scope Guava's Objects class in the code to avoid a naming collision with Java SE 7's Objects class. Of course, if one is not using Java 7, then this is not an issue and if one is using Java 7, it's most likely that the standard version should be used instead anyway.

Conclusion

Guava provides a nice approach for building safer and more readable common methods via its Objects class. Although I'll use the new java.util.Objects class instead for JDK 7 projects, Guava's com.google.common.base.Objects class provides a nice alternative for working in versions of Java prior to JDK 7.

Guava's Bidirectional Maps

Google Guava has much to offer the Java developer working with J2SE 5, Java SE 6, or Java SE 7. The older the version of these being used, the more useful Guava can be. Although Java SE 7 brings select Guava-provided functionality to the Java programming language as a standard part of the language (such as the new Objects class), there are still numerous features of Guava that are not available even in JDK 7. In this post, I focus on Guava's support of bidirectional maps.

Guava's heritage is in the "ancient and unmaintained" Google Collections project and Guava's bidirectional map support comes from that Google Collections heritage. The API documentation for Guava's com.google.common.collect.BiMap interface provides a nice concise definition of a bidirectional map:

A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as that of its keys. This constraint enables bimaps to support an "inverse view", which is another bimap containing the same entries as this bimap but with reversed keys and values.

I have run into several situations during my career where bidirectional map support is helpful in making clearer and more readable code. I have even built custom implementations of bidirectional maps, but no longer do that thanks to the availability of Guava (and previously of Google Collections and Apache Commons's BidiMap). The next code listing shows a simple situation where a bidirectional map is useful. This example maps nations to their capital cities. The beauty of the bidirectional map is that I can look up a capital city by its nation's name or I can look up a nation's name by the name of the capital city. An important characteristic of the bidirectional map is that the "value" side of the bidirectional map requires unique values in addition to the more typical "key" side of the bidirectional map requiring unique values.

TwoMonoDirectionalMaps
package dustin.examples;

import static java.lang.System.out;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Demonstrate simplistic implementation of functionality equivalent to that
 * provided by bidirectional map via two monodirectional maps. This class has
 * some intentional problems to illustrate the maintenance disadvantages of this
 * approach. For example, there is a mismatch between the two single-direction
 * maps for "London" (UK in one case and England in the other case) and the
 * mapping for France/Paris was left off one of the single-direction maps.
 * 
 * @author Dustin
 */
public class TwoMonoDirectionalMaps
{
   private final static Map<String, String> nationsToCapitals;
   private final static Map<String, String> capitalsToNations;

   static
   {
      final Map<String, String> tempNationsToCapitals = new HashMap<String, String>();
      tempNationsToCapitals.put("Canada", "Ottawa");
      tempNationsToCapitals.put("England", "London");
      tempNationsToCapitals.put("France", "Paris");
      tempNationsToCapitals.put("Mexico", "Mexico City");
      tempNationsToCapitals.put("Portugal", "Lisbon");
      tempNationsToCapitals.put("Spain", "Madrid");
      tempNationsToCapitals.put("United States", "Washington");
      nationsToCapitals = Collections.unmodifiableMap(tempNationsToCapitals);

      final Map<String, String> tempCapitalsToNations = new HashMap<String, String>();
      tempCapitalsToNations.put("Lisbon", "Portugal");
      tempCapitalsToNations.put("London", "United Kingdom");
      tempCapitalsToNations.put("Madrid", "Spain");
      tempCapitalsToNations.put("Mexico City", "Mexico");
      tempCapitalsToNations.put("Ottawa", "Canada");
      tempCapitalsToNations.put("Washington", "United States");
      capitalsToNations = Collections.unmodifiableMap(tempCapitalsToNations);
   }

   /**
    * Print the capital city of the nation whose name is provided.
    * 
    * @param nationName Name of nation for which capital is decided.
    */
   public void printCapitalOfNation(final String nationName)
   {
      out.println(
           "The capital of " + nationName + " is "
         + (nationsToCapitals.containsKey(nationName) ? nationsToCapitals.get(nationName) : "unknown" )
         + ".");
   }

   /**
    * Print the name of the nation whose capital name is provided.
    * 
    * @param capitalName Name of capital city for which nation is desired.
    */
   public void printNationOfCapital(final String capitalName)
   {
      out.println(
           capitalName + " is the the capital of "
         + (capitalsToNations.containsKey(capitalName) ? capitalsToNations.get(capitalName) : "unknown" )
         + ".");
   }

   /**
    * Main function demonstrating this use of two mono-directional maps.
    * 
    * @param arguments Command-line arguments; none expected.
    */
   public static void main(final String[] arguments)
   {
      final TwoMonoDirectionalMaps me = new TwoMonoDirectionalMaps();
      me.printCapitalOfNation("United States");
      me.printCapitalOfNation("England");
      me.printCapitalOfNation("France");
      me.printNationOfCapital("Washington");
      me.printNationOfCapital("London");
      me.printNationOfCapital("Paris");
   }
}

When the above is run, the output looks like that shown in the next screen snapshot. The names for capital cities and nations that are provided in the example are intended to illustrate one of the problems with the approach of using two single-directional maps: they can get out of synch. This issue can be remedied with a bidirectional map as shown in the next code sample that employs the ImmutableBiMap implementation of the BiMap interface.

GuavaBiMapDemo
package dustin.examples;

import static java.lang.System.out;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Simple demonstration of Google Guava's bidirectional map support.
 * 
 * @author Dustin
 */
public class GuavaBiMapDemo
{
   private final static BiMap<String, String> nationsToCapitals;

   static
   {
      final Map<String, String> tempNationsToCapitals = new HashMap<String, String>();
      tempNationsToCapitals.put("Canada", "Ottawa");
      tempNationsToCapitals.put("England", "London");
      tempNationsToCapitals.put("France", "Paris");
      tempNationsToCapitals.put("Mexico", "Mexico City");
      tempNationsToCapitals.put("Portugal", "Lisbon");
      tempNationsToCapitals.put("Spain", "Madrid");
      tempNationsToCapitals.put("United States", "Washington");
      nationsToCapitals = ImmutableBiMap.copyOf(Collections.unmodifiableMap(tempNationsToCapitals));
   }

   /**
    * Print the capital city of the nation whose name is provided.
    * 
    * @param nationName Name of nation for which capital is decided.
    */
   public void printCapitalOfNation(final String nationName)
   {
      out.println(
           "The capital of " + nationName + " is "
         + (nationsToCapitals.containsKey(nationName) ? nationsToCapitals.get(nationName) : "unknown" )
         + ".");
   }

   /**
    * Print the name of the nation whose capital name is provided.
    * 
    * @param capitalName Name of capital city for which nation is desired.
    */
   public void printNationOfCapital(final String capitalName)
   {
      out.println(
           capitalName + " is the the capital of "
         + (nationsToCapitals.containsValue(capitalName) ? nationsToCapitals.inverse().get(capitalName) : "unknown" )
         + ".");
   }

   /**
    * Main function demonstrating this use of two mono-directional maps.
    * 
    * @param arguments Command-line arguments; none expected.
    */
   public static void main(final String[] arguments)
   {
      final GuavaBiMapDemo me = new GuavaBiMapDemo();
      me.printCapitalOfNation("United States");
      me.printCapitalOfNation("England");
      me.printCapitalOfNation("France");
      me.printNationOfCapital("Washington");
      me.printNationOfCapital("London");
      me.printNationOfCapital("Paris");
   }
}

When the above is executed, the output shows more consistent mappings for London and for Paris/France because there was no need to maintain two separate maps.

Guava's bidirectional maps provide a safer and more readable approach to implementing data structures that map keys to values and values to key in such a way that one can be accessed via the other. Normal Java Maps only access use of a key to access a value directly, but both directions of access are supported by Guava's bidirectional maps.

Guava seems to be receiving more attention these days. Google's guava java: the easy parts was written about one year ago and is a nice introduction to the "easier" portions of Guava. Tom Jefferys's September 2011 post Multimaps - Google Guava also provides a nice overview of Guava's map support with focus on Multimaps followed by separate posts focusing on BiMaps and Multisets. Recent post 5 Reasons to use Guava includes Guava's map support as one of five reasons that Java developers should embrace Guava. Section 16.9. retrieving a key by value (working with bi-directional maps) of the Java Commons Cookbook (download) also covers Guava's support for bidirectional maps.