Showing posts with label optional. Show all posts
Showing posts with label optional. Show all posts

Monday, December 25, 2017

Java 9: Enhancements to Optional

Previously, I wrote about the Optional class that was introduced in Java 8 to model potentially absent values and reduce the number of places where a NullPointerException could be thrown.

Java 9 adds three new methods to Optional:

1. ifPresentOrElse

The new ifPresentOrElse method allows you to perform one action if the Optional is present and a different action if the Optional is not present. For example:

lookup(userId).ifPresentOrElse(this::displayUserDetails,
                               this::displayError)

2. stream

The new stream method makes it easier to convert a stream of Optional objects into a stream of values that are present in them. Previously (in Java 8), you needed two steps in order to achive this. First, you would filter out the empty Optionals and then you would unbox the rest in order to get their values. This is shown below:

// In Java 8:
Stream.of("alice", "bob", "charles")
      .map(UserDirectory::lookup)
      .filter(Optional::isPresent)
      .map(Optional::get)
      .collect(toList());

In Java 9, the code becomes simpler using the stream method:

// In Java 9:
Stream.of("alice", "bob", "charles")
      .map(UserDirectory::lookup)
      .flatMap(Optional::stream)
      .collect(toList());

3. or

The or method is somewhat similar to the orElseGet method but returns Optional objects instead of values. If a value is present, it returns the existing Optional. If the value is not present, it returns the Optional produced by the supplying function. For example:

lookup(userId).or(() -> lookupInAnotherDatabase(userId));

Monday, May 25, 2015

Java 8: Filtering an Optional

In my previous post, I wrote about how you can use optionals to model the absence of a value. Optionals also contain a useful filter method, which takes a predicate as an argument. If a value is present in the Optional object and the predicate is satisfied, the filter method returns that value; otherwise it returns an empty Optional object.

For example, let's suppose that the Phone class of the Person/Phone/Camera/Resolution model has a method to get the operating system of the phone, and we want to get the camera resolution of "Android" phones only. We can modify the getPhoneCameraResolution method to use a filter, as shown below:

public Resolution getPhoneCameraResolution(final Optional<Person> person) {
  return
    person.flatMap(Person::getPhone)  // returns Optional<Phone>
          .filter(phone -> phone.getOS() == OS.Android) // match Android phones only
          .flatMap(Phone::getCamera) // returns Optional<Camera>
          .map(Camera::getResolution) // returns Optional<Resolution>
          .orElse(Resolution.UNKNOWN); // returns Resolution or UNKNOWN if not found
}

Related posts:
Java 8: Using Optional Objects

Saturday, May 23, 2015

Java 8: Using Optional Objects

Consider the following nested object structure for a person with a camera phone:

class Person {
  private Phone phone;

  public Phone getPhone() {
    return phone;
  }
}

class Phone {
  private Camera camera;

  public Camera getCamera() {
    return camera;
  }
}

class Camera {
  private Resolution resolution;

  public Resolution getResolution() {
    return resolution;
  }
}

class Resolution {
  private int width;
  private int height;
}

Now, let's say that you want to get the resolution of a person's camera phone.

You might be tempted to do this:

public Resolution getPhoneCameraResolution(Person person) {
  return person.getPhone().getCamera().getResolution();
}

However, not everyone owns a phone, so person.getPhone() might return null and consequently person.getPhone().getCamera() will result in a NullPointerException at run-time! Similarly, a person might own a phone, but the phone might not have a camera.

One way to avoid NullPointerExceptions is to check for nulls, as shown below:

public Resolution getPhoneCameraResolution(Person person) {
  if (person != null) {
    Phone phone = person.getPhone();
    if (phone != null) {
      Camera camera = phone.getCamera();
      if (camera != null) {
        return camera.getResolution();
      }
    }
  }
  return Resolution.UNKNOWN;
}

This code doesn't scale very well and makes your code harder to read and maintain. Every time a variable could be null, you have to add another nested if statement.

Java 8: Optional class

Java 8 introduced a new class called java.util.Optional<T> to model potentially absent values. It forces you to actively unwrap an optional and deal with the absence of a value. It also leads to better APIs because, just by reading the signature of a method, you can tell whether to expect an optional value.

We can re-write the original object model using the Optional class as shown below:

import java.util.Optional;

class Person {
  private Optional<Phone> phone;

  public Optional<Phone> getPhone() {
    return phone;
  }
}

class Phone {
  private Optional<Camera> camera;

  public Optional<Camera> getCamera() {
    return camera;
  }
}

class Camera {
  // resolution is not optional - all cameras must have it
  private Resolution resolution;

  public Resolution getResolution() {
    return resolution;
  }
}

Creating Optional objects:

  • Empty Optional: You can create an empty optional object use Optional.empty:
    Optional<Phone> optPhone = Optional.empty();
    
  • Optional from a non-null value: To create an optional from a non-null value use Optional.of. Note that if the value is null a NullPointerException is thrown immediately:
    Optional<Phone> optPhone = Optional.of(phone);
    
  • Optional from null: Use Optional.ofNullable to create an optional that may hold a null value. If the value is null, an empty optional is created.
    Optional<Phone> optPhone = Optional.ofNullable(phone);
    

Extracting values from Optional objects:

Optional supports a map method, which can be used to extract information from an object. For example, to get the Resolution of a Camera:

Optional<Camera> optCamera = Optional.ofNullable(camera);
Optional<Resolution> resolution = optCamera.map(Camera::getResolution);

What this means is that if the camera optional contains a value, getResolutions is called, otherwise nothing happens and an empty optional is returned.

Optional also contains a flatMap method in order to "flatten" nested optionals. For example, if you were to call map with a function that returns an Optional, you will get an Optional containing an Optional, as illustrated below:

Optional<Phone> optPhone = Optional.ofNullable(phone);

// calling map returns a two-level optional
Optional<Optional<Camera>> optOptCamera = optPhone.map(Phone::getCamera);

// but flatMap, returns a single-level optional
Optional<Camera> optCamera = optPhone.flatMap(Phone::getCamera);

Chaining Optional objects:

Now let's get back to our original problem of getting the resolution of a person's camera phone and re-write those ugly, nested if-statements using optionals instead:

public Resolution getPhoneCameraResolution(final Optional<Person> person) {
  return
    person.flatMap(Person::getPhone)  // returns Optional<Phone>
          .flatMap(Phone::getCamera) // returns Optional<Camera>
          .map(Camera::getResolution) // returns Optional<Resolution>
          .orElse(Resolution.UNKNOWN); // returns Resolution or UNKNOWN if not found
}

This code returns the resolution of the person's camera phone. If the person is null, doesn't have a phone, or the phone doesn't have a camera, an UNKNOWN resolution is returned.