Friday, February 21, 2020

Guide to Using Optional in Java 8

Introduction


When writing any kind of code in Java, developers tend to work with objects more often than with primitive values (int, boolean, etc). This is because objects are at the very essence of object-oriented programming: they allow a programmer to write abstract code in a clean and structured manner.


Furthermore, every object in Java can either contain a value or not. If it does, its value is stored on the heap and the variable which we are using has a reference to that object. If the object contains no value, this defaults to null – a special placeholder denoting the absence of a value.


The fact that each object can become null, combined with the natural tendency to use objects instead of primitives, means that some arbitrary piece of code might (and often-times will) result in an unexpected NullPointerException.


Before the Optional class was introduced in Java 8, these kind of NullPointerException errors were much more common in everyday life of a Java programmer.


In the following sections, we will dive deeper into explaining Optional and seeing how it can be used to overcome some of the common problems concerning null values.


The Optional Class


An Optional is essentially a container. It is designed either to store a value or to be "empty" if the value is non-existent – a replacement for the null value. As we will see in some later examples, this replacement is crucial as it allows implicit null-checking for every object represented as an Optional.


This means that explicit null-checking is no longer needed from a programmer’s point of view – it becomes enforced by the language itself.


Creating Optionals


Let’s take a look at how easy it is to create instances of Optional and wrap objects that we already have in our applications.


We’ll be using our custom class for this, the Spaceship class:


public class Spaceship private Engine engine; private String pilot; // Constructor, Getters and Setters


And our Engine looks like:


public class Engine private VelocityMonitor monitor; // Constructor, Getters and Setters


And furthermore, we’ve got the VelocityMonitor class:


public class VelocityMonitor private int speed; // Constructor, Getters and Setters


These classes are arbitrary and only serve to make a point, there’s no real implementation behind them.


of()


The first approach to creating Optionals is using the .of() method, passing a reference to a non-null object:


Spaceship falcon = new Spaceship();
Optional<Spaceship> optionalFalcon = Optional.of(falcon);

If the falcon was null, the method .of() would throw a NullPointerException.


Without Optional, trying to access any of the fields or methods of falcon (assuming it’s null), without performing a null-check would result in a crash of the program.


With Optional, the .of() method notices the null value and throws the NullPointerException immediately – potentially also crashing the program.



If the program crashes in both approaches, why even bother using Optional?



The program wouldn’t crash somewhere deeper in the code (when accessing falcon) but at the very first use (initialization) of a null object, minimizing potential damage.


ofNullable()


If falcon is allowed to be a null, instead of the .of() method, we’d use the .ofNullable() method. They perform the same if the value is non-null. The difference is obvious when the reference points to null in which case – the .ofNullable() method is perfectly contempt with this piece of code:


Spaceship falcon = null;
Optional<Spaceship> optionalFalcon = Optional.ofNullable(falcon);

empty()


And finally, instead of wrapping an existing reference variable (null or non-null), we can create a null value in the context of an Optional. It’s kind of like an empty container which returns an empty instance of Optional:


Optional<Spaceship> emptyFalcon = Optional.empty();

Checking for Values


After creating Optionals and packing information in them, it’s only natural that we’d want to access them.


Before accessing though, we should check if there are any values, or if the Optionals are empty.


isPresent()


Since catching exceptions is a demanding operation, it would be better to use one of the API methods to check if the value exists before trying to access it – and alter the flow if it doesn’t.


If it does, then .get() method can be used to access the value. Though, more on that method in latter sections.


To check if the value is present inside an Optional, we use the .isPresent() method. This is essentially a replacement for the null-check of the old days:


// Without Optional
Spaceship falcon = hangar.getFalcon();
if (falcon != null) System.out.println(falcon.get());
else System.out.printn("The Millennium Falcon is out and about!");
// With Optional
Optional<Spaceship> optionalFalcon = Optional.ofNullable(hangar.getFalcon());
if (optionalFalcon.isPresent()) System.out.println(falcon.get());
else System.out.println("The Millennium Falcon is out and about!");


Since the falcon can also not be in the hangar, we can also expect a null value, thus .ofNullable() is used.


ifPresent()


To make things even easier, Optional also contains a conditional method which bypasses the presence check entirely:


Optional<Spaceship> optionalFalcon = Optional.ofNullable(hangar.getFalcon());
optionalFalcon.ifPresent(System.out::println);

If a value is present, the contents are printed through a Method Reference. If there’s no value in the container, nothing happens. You still might want to use the previous approach if you’d like to define an else statement, though.


This reflects what we mentioned earlier when we said that null-checks with Optional are implicit and enforced by the type-system.


isEmpty()


Another way to check for a value is to use .isEmpty(). Essentially, calling Optional.isEmpty() is the same as calling !Optional.isPresent(). There’s no particular difference that exists:


Optional<Spaceship> optionalFalcon = Optional.ofNullable(hangar.getFalcon());
if (optionalFalcon.isEmpty()) System.out.println("Please check if the Millennium Falcon has returned in 5 minutes.");
else optionalFalcon.doSomething();


Nested Null-Checks


Our Spaceship class, as defined earlier, has an attribute Engine, which has an attribute VelocityMonitor.


Suppose now that we want to access the velocity monitor object and obtain the current velocity of the spaceship, taking into consideration that all these values could potentially be null.


Obtaining the velocity might look something like this:


if (falcon != null) Engine engine = falcon.getEngine(); if (engine != null) VelocityMonitor monitor = engine.getVelocityMonitor(); if (monitor != null) Velocity velocity = monitor.getVelocity(); System.out.println(velocity); 


The above example shows how tedious it is to perform such checks, not to mention the amount of boilerplate code needed to make the checks possible in the first place.


An alternative solution using Optional would be:


Velocity velocity = falcon .flatMap(Spaceship::getEngine) .flatMap(Engine::getVelocityMonitor) .map(VelocityMonitor::getVelocity);

Note: Not sure what’s going on above? Check out the explanation below for the details.


Using this kind of approach, no explicit checks are needed. If any of the objects contain an empty Optional, the end result will also be an empty Optional.


To make things work like this, we need to modify our existing definitions of the Spaceship and Engine classes:


public class Spaceship private Optional<Engine> engine; private String pilot; // Constructor, Getters and Setters


public class Engine private Optional<VelocityMonitor> monitor; // Constructor, Getters and Setters


What we have changed are the attribute definitions: they are now wrapped inside Optional objects to make this kind of alternative solution possible.


This might seem a bit tedious at first but if planned from the beginning, it takes almost the same amount of effort typing it.


Furthermore, having an Optional attribute instead of a regular object reflects the fact that the attribute might or might not exist. Notice how this is quite helpful since we don’t have semantic meanings of this kind with regular attribute definitions.


Example Explanation


In this section, we’ll take a bit of time to explain the previous example with flatMaps and maps. If you understand it without further explanation, please feel free to skip this section.


The first method call is performed on falcon which is of type Optional<Spaceship>. Calling the getEngine method returns an object of type Optional<Engine>. Combining these two types, the type of the returned object becomes Optional<Optional<Engine>>.


Since we would like to view this object as an Engine container and perform further calls on it, we need some kind of mechanism to "peel off" the outer Optional layer.


Such a mechanism exists and it is called flatMap. This API method combines the map and the flat operations by first applying a function to each of the elements and then flattening the result into a one-level stream.


The map method, on the other hand, only applies a function without flattening the stream. In our case, the use of map and flatMap would give us Optional<Optional<Engine>> and Optional<Engine> respectively.


Calling flatMap on an object of type Optional would therefore yield with a one-level Optional, allowing us to use multiple similar method calls in a succession.


This finally leaves us withOptional<Engine>, which we wanted in the first place.


Alternative results


.orElse()


The previous example can be further expanded by using the orElse(T other) method. The method will return the Optional object upon which it is called only if there is a value contained within it.


If the Optional is empty, the method returns the other value. This is essentially an Optional version of the ternary operator:


// Ternary Operator
Spaceship falcon = maybeFalcon != null ? maybeFalcon : new Spaceship("Millennium Falcon"); // Optional and orElse()
Spaceship falcon = maybeFalcon.orElse(new Spaceship("Millennium Falcon"));

As with the ifPresent() method, this kind of approach takes advantage of the lambda expressions to make code more readable and less error-prone.


.orElseGet()


Instead of providing the other value directly as an argument, we can use a

Supplier instead. The difference between .orElse() and .orElseGet(), while maybe not evident at first glance, exists:


// orElse()
Spaceship falcon = maybeFalcon.orElse(new Spaceship("Millennium Falcon")); // orElseGet()
Spaceship falcon = maybeFalcon.orElseGet(() -> new Spaceship("Millennium Falcon"));

If maybeFalcon doesn’t contain a value, both methods will return a new Spaceship. In this case, their behavior is the same. The difference becomes clear if maybeFalcon does contain a value.


In the first case, the new Spaceship object will not be returned but it will be created. This will happen regardless of whether or not the value exists. In the second case, the new Spaceship will be created only if maybeFalcon doesn’t contain a value.


It’s similar to how do-while does the task regardless of the while loop, at least once.


This might seem like a negligible difference but it becomes pretty important if creating spaceships is a demanding operation. In the first case, we’re always creating a new object – even if it will never be used.


.orElseGet() should be preferred instead of .orElse() in such cases.


.orElseThrow()


Instead of returning an alternative value (as we’ve seen in the previous two sections), we can throw an exception. This is accomplished with the .orElseThrow() method which instead of an alternative value accepts a supplier which returns the exception in case it needs to be thrown.


This can be useful in cases where the end result is of high importance and must not be empty. Throwing an exception in this case might be the safest option:


// Throwing an exception
Spaceship falcon = maybeFalcon.orElseThrow(NoFuelException::new);

Getting Values from Optional


.get()


After seeing many different ways of checking and accessing the value inside Optional, let’s now take a look at one final way of obtaining the value which also uses some of the previously shown methods.


The simplest way to access a value inside an Optional is with .get(). This method returns the value present, or throws a NoSuchElementException if the value is absent:


Optional<Spaceship> optionalFalcon = Optional.ofNullable(hangar.getFalcon());
if (falcon.isPresent()) Spaceship falcon = optionalFalcon.get() // Fly the falcon


As expected, the .get() method returns a non-null instance of the Spaceship class and assigns it to the falcon object.


Conclusion


Optional was introduced to Java as a way to fix the issues with null references. Before Optional, every object was allowed to either contain a value or not (i.e. being null).


The introduction of Optional essentially enforces null-checking by the type-system making it unnecessary to perform such checks manually.


This was a big step both in improving the language and its usability by adding an additional layer of type-checking. Using this system instead of the old-fashioned null-checking allows writing clear and concise code without the need to add boilerplate and perform tiring checks by hand.

No comments:

Post a Comment