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.

8 comments:

mario.gleichmann said...

Nice article!
The Option type (or sometimes called Maybe) is an old hat in the functional world ...

It's even nothing new in the Java env, since it can be easily 'transmitted' from Haskell, Scala or any other language which supports Option ...

Yoy may want to have a look at

https://github.com/mariogleichmann/functJ/blob/master/test/funct/monad/OptionTest.java

for using Option in Java in a more 'monadic' style without worrying about 'NullPointers' at all ...

Greetings

Mario

@DustinMarx said...

mario.gleichmann,

Thanks for the feedback and for reference to your functj project.

Dustin

CodeBleep said...

Good article. Just curious why you did not use a type parameter on the Optional return arg?

@DustinMarx said...

CodeBleep,

Thanks for the comment. I realized I had forgotten to change the less-than symbols and greater-than symbols to their corresponding entity references before posting that code sample. I have converted all of them so that the appropriate parameterized types now show up.

Thanks for pointing that out.

Dustin

@DustinMarx said...

François Sarradin's blog post From Optional to Monad with Guava is an interesting look at use of Guava's Optional class.

@DustinMarx said...

A summary of the reasoning behind Optional's API? provides some interesting details about Google, Guava, and Optional.

How to use Guava Optional as "naturally covariant object" also discusses Optional.

Dustin

@DustinMarx said...

Optional is coming to JDK 8! See Java 8 Optional Objects and On Java 8′s introduction of Optional for more details.

Unknown said...

Loved this, quite helpful.