Optional makes your code readable and avoids null pointer exceptions.

It is said that programmers who have never encountered null-pointer exceptions are not Java programmers, and null does cause a lot of problems. A new class called java.util.Optional was introduced in Java 8 to avoid many of the problems caused by NULL.

Let’s take a look at what null references can do. First create a class Computer with the structure shown below:

What happens when we call the following code?

String version = computer.getSoundcard().getUSB().getVersion();
Copy the code

The above code seems fine, but many computers (such as raspberry PI) don’t have sound cards, and calling getSoundcard() will definitely throw a null pointer exception.

A common but bad way to do this is to return a null reference to indicate that the computer has no sound card, but this means calling the getUSB() method on an empty argument, apparently throwing a control exception while the program is running, causing it to stop running. Think about how embarrassing it would be to have this error pop up when your program is running on a client computer. The great computer scientist Tony Hoare once wrote, “I think null references were created in 1965 and cost a billion dollars. My biggest temptation to use null references was that it was easy to implement.

So how do you avoid null-pointer exceptions when your program is running? You need to be alert and constantly check for null Pointers, like this:

String version = "UNKNOWN"; if(computer ! = null) { Soundcard soundcard = computer.getSoundcard(); if(soundcard ! = null){ USB usb = soundcard.getUSB(); if(usb ! = null){ version = usb.getVersion(); }}}Copy the code

However, you can see that the above code has too many null checks and the entire code structure becomes very ugly. However, we have to use this judgment to ensure that the system does not run with null Pointers. A large number of such null-reference judgments in our business code would be annoying and would result in poor readability of our code.

Null references are also potentially problematic if you forget to check that the value is null. In this article I will prove that using null references is a bad way to indicate that a value does not exist. Instead of using null references, we need a better representation of non-existent values.

Java 8 introduces a new class called java.util.Optional

, which is inspired by the Haskell and Scala languages. This class can contain an arbitrary value, as shown in the figure and code below. You can think of Optional as a value that may contain a value. If Optional does not contain a value, it is empty, as shown in the figure below.

public class Computer { private Optional<Soundcard> soundcard; public Optional<Soundcard> getSoundcard() { ... }... } public class Soundcard { private Optional<USB> usb; public Optional<USB> getUSB() { ... } } public class USB{ public String getVersion(){ ... }}Copy the code

The code above shows that it is possible for a computer to replace a sound card (which may or may not exist). Sound cards may also include a USB port. This is an improvement, the model can reflect more clearly that a given value can not exist.

But what about the Optional

object? After all, you want to get the USB port number. Quite simply, the Optional class contains methods to handle the presence or absence of a value. In contrast to null references, the Optional class forces you to handle whether or not you want the value to be relevant, thus avoiding null pointer exceptions.

Note that the Optional class is not intended to replace null references. Instead, it is designed to make the API easier to understand, so that when you see a function’s signature, you can determine if the value to be passed to the function might not exist. This forces you to open the Optional class to handle the true value case.

Optional mode

So much wordy, look at some code! Let’s take a look at how Optional overrides traditional NULL reference detection. You will see how to use Optional at the end of this article.

String name = computer.flatMap(Computer::getSoundcard)
                          .flatMap(Soundcard::getUSB)
                          .map(USB::getVersion)
                          .orElse("UNKNOWN");
Copy the code

Create an Optional object

You can create an empty Optional object:

Optional<Soundcard> sc = Optional.empty();
Copy the code

Next create an Optional value that contains a non-null value:

SoundCard soundcard = new Soundcard();
Optional<Soundcard> sc = Optional.of(soundcard);
Copy the code

If the sound card is null, the null-pointer exception is thrown immediately (which is better than being thrown when the sound card property is acquired).

By using ofNullable, you can create an Optional object that may contain a NULL reference:

Optional<Soundcard> sc = Optional.ofNullable(soundcard);
Copy the code

If the sound card is a null reference, the Optional object is empty.

Processing of values in Optional

Now that you have the Optional object, you can call the corresponding method to handle whether the value in the Optional object exists. Instead of null detection, we can use the ifPresent() method, as follows:

Optional<Soundcard> soundcard = ... ; soundcard.ifPresent(System.out::println);Copy the code

This eliminates the need for null checks, and if the Optional object is empty, no information will be printed.

You can also use the isPresent() method to see if the Optional object really exists. In addition, there is a get() method that returns the value contained in the Optional object, if one exists. Otherwise, a NoSuchElementException is thrown. The two methods can be used together like this to avoid exceptions:

if(soundcard.isPresent()){
  System.out.println(soundcard.get());
}

Copy the code

While this approach is not recommended (it’s not much of an improvement over null detection), we’ll take a look at the working conventions below.

Returns the default values and related actions

A normal operation when null is encountered is to return a default value. You can use a ternary expression to do this:

Soundcard soundcard = maybeSoundcard ! = null ? maybeSoundcard : new Soundcard("basic_sound_card");Copy the code

With Optional objects, you can override orElse(). OrElse () returns a default value when Optional is empty:

Soundcard soundcard = maybeSoundcard.orElse(new Soundcard("defaut"));
Copy the code

Similarly, orElseThrow() can be used when Optional is empty:

Soundcard soundcard = 
  maybeSoundCard.orElseThrow(IllegalStateException::new);

Copy the code

Use filter to filter specific values

We often call methods on an object to determine its properties. For example, you might want to check if the USB port number is a specific value. To be safe, you need to check if the medical point to USB is null, and then call getVersion() like this:

USB usb = ... ; if(usb ! = null && "3.0" equals (usb) getVersion ())) {System. Out. Println (" ok "); }Copy the code

If you use Optional, you can override it with the filter function:

Optional<USB> maybeUSB = ... ; Maybeusb.filter (USB -> "3.0".equals(usb.getVersion()).ifPresent(() -> system.out.println ("ok"));Copy the code

The filter method requires a predicate pair as an argument. If the values in Optional exist and satisfy the predicate, the filter function will return the values that satisfy the predicate; Otherwise, an empty Optional object is returned.

Map method is used to extract and transform data

A common pattern is to extract some properties of an object. For example, for a Soundcard object, you might want to get its USB object and determine its version number. The way we usually implement this is as follows:

if(soundcard ! = null){ USB usb = soundcard.getUSB(); if(usb ! = null && "3.0".equals(usb.getVersion()){system.out.println ("ok"); }}Copy the code

We can override this null-detection and then extract objects of object type using the map method.

Optional<USB> usb = maybeSoundcard.map(Soundcard::getUSB);
Copy the code

This is the same as the map function using stream. Using a stream requires passing a function to the map function as an argument that will be applied to each element in the stream. Nothing happens when the stream is in time and space.

The value contained in Optional will be converted by the function passed in (in this case, a function that gets the USB from the sound card). If the Optional object is spatially ephemeral, nothing happens.

Then, we combine the map method and filter method to filter out USB sound cards whose version number is not 3.0.

Maybesoundcard.map (Soundcard::getUSB).filter(USB -> "3.0".equals(usB.getVersion ()).ifPresent(() -> System.out.println("ok"));Copy the code

So our code starts to look a little bit like what we gave it at the beginning, without null detection.

Pass Optional objects using the flatMap function

Now that we’ve seen an example of code that can be refactored with Optional, how can we implement the following code in a secure way?

String version = computer.getSoundcard().getUSB().getVersion();
Copy the code

Note that all of the above code extracts one object from another, using the map function. In the previous article we set up the Computer to contain an Optional<Soundcard> object and the Soundcard to contain an Optional<USB> object, so we can refactor the code this way

String version = computer.map(Computer::getSoundcard)
                  .map(Soundcard::getUSB)
                  .map(USB::getVersion)
                  .orElse("UNKNOWN");
Copy the code

Unfortunately, the above code compiles incorrectly, so why? The computer variable is of type Optional< computer >, so it is fine to call the map function. But the getSoundcard() method returns an Optional

object, returns an Optional
> object, and makes a second map call, As a result, calling getUSB() becomes illegal. The following diagram depicts this scenario:

The source implementation of the map function looks like this:

 public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
Copy the code

Option.ofnullable () is called again, which returns Optional
>

Optional provides the flatMap function, which is designed to compress a two-level Optional into one when converting the value of the Optional object (like the map operation). The following figure shows the difference between Optional objects that type by calling map and flatMap:

So we can write this:

String version = computer.flatMap(Computer::getSoundcard)
                   .flatMap(Soundcard::getUSB)
                   .map(USB::getVersion)
                   .orElse("UNKNOWN");

Copy the code

The first flatMap ensures that it returns Optional

instead of Optional
>, and the second flatMap does the same and returns Optional

. Note that map() is called the third time, because getVersion() returns a String object instead of an Optional object.


We’ve finally changed the ugly nested null checks we started with into more readable code, and avoided null-pointer exceptions.

conclusion

In this article we have adopted the new class java.util.Optional

provided by Java 8. This class is not intended to replace null references, but rather to help designers design better apis that can tell by reading a function’s signature whether it accepts a value that may or may not exist. In addition, Optional forces you to turn on Optional and then handle whether the value exists, which allows your code to avoid potential null-pointer exceptions.

The last

Thank you for reading. If you are interested, you can follow the wechat public account to get the latest pushed articles.