Effective Java! Return Optionals Judiciously

Kyle Carter - Dec 24 '20 - - Dev Community

Prior to Java 8 if a method didn't want to return a value some of the time and return a value at other times there were a few options. The method could return a null, the method could throw an exception, or you could come up with some state holding object that could be returned (although I've never seen this). The first two options aren't great. Returning nulls is just asking for Null pointer exceptions. Throwing exceptions is extremely heavy handed, slow, and a pain to deal with. With Java 8 the language provided a standard way to use the third option with the introduction of the Optional class. Conceptually an Optional is an immutable collection that can hold at most one item. While it doesn't actually implement the Collection interface it could. On top of this state holding behavior it also has a number of helpful methods that really make it useful.

In a previous chapter we talked about a method that could calculate the maximum value in a collection.

public static <E extends Comparable<E>> E max(Collection<E> collection) {
  if (collection.isEmpty()) {
    throw new IllegalArgumentException("Empty collection");
  }
  E maximumValue = null;
  // Insert business logic for calculating maximum
  return maximumValue. 
}
Enter fullscreen mode Exit fullscreen mode

The above method takes the stance of throwing exceptions when it doesn't have anything to return. We can choose to not do this and instead convert to the use of optionals.

public static <E extends Comparable<E>> Optional<E> max(Collection<E> collection) {
  E maximumValue = null;
  // Insert business logic for calculating maximum
  return Optional.ofNullable(maximumValue). 
}
Enter fullscreen mode Exit fullscreen mode

As we can see above this actually even simplifies the code. If Optional.ofNullable is passed a null it will return the same things as Optional.empty() and if it is passed a concrete object it will return the object wrapped in an Optional (this can also be done manually by using Optional.of(), it simply doesn't do the null check for you).

Something of note, an Optional is just another class and thus you can return null from methods that return Optionals. While the language allows this unfortunately you should not do this. This ruins the point of the Optional and you lose all faith in the writers of code that do this.

Another interesting thing is that Streams and Optionals entered the language at the same time. This being the case there are many terminal operations in the Streams API that return Optionals. Using this we can actually make our max function even more concise.

public static <E extends Comparable<E>> Optional<E> max(Collection<E> collection) {
  return collection.stream().max(Comparator.naturalOrder()); 
}
Enter fullscreen mode Exit fullscreen mode

Our whole method has turned into a one-liner.

Another thing that you may discover as you work with Optional returning methods is that they have the same spirit as checked exceptions. This is because they force the caller of your method to confront the possibility of an empty value. While I'm not a fan of checked exceptions I am a fan of Optionals. Confronting this fact I think it is because of two reasons. 1. Optionals are less invasive to deal with. 2. Optionals provide functions that are very useful when dealing with potential emptiness that take them above and beyond simple state holders.

What about these methods that I speak of? There are many of these functions and I can't cover them all but they range from simple things like isPresent() and orElse(E) to more fun things like map(). As you use Optionals you discover these methods and they really can change the way you code, very similar to Streams. While the easiest thing is usually to use ifPresent() off the bat, I challenge you to try using the higher-powered methods and see how it works for you.

So when should Optionals not be used. One case would be when using a container class already such as a collection. In these cases simply return an empty collection like discussed in our previous installment of this series. Honestly this is the main reason I wouldn't use an Optional with a method that may return an empty result.

Creating Optional objects does have a cost, just like creating any object. This is especially prevalent with boxed types. This is the reason why there are special versions of Optionals for these (OptionalInt, OptionalLong, etc). When interacting with these types, always use the specialized Optional type.

As a final grab bag of items to consider let's go over some final thoughts. It's almost never appropriate to use an Optional as a key into an object. Also taking an Optional as a parameter is a smell. In these cases you will likely just want to overload the method and provide both options of passing the parameter and not.

In my view Optionals were a great addition to the language. Although there are more powerful equivalent types in some other languages I think this class does a great job for something added so late into a language. Hopefully this chapter review gives you a little more confidence when using Optionals.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .