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 NullPointerException
s 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 isnull
aNullPointerException
is thrown immediately:Optional<Phone> optPhone = Optional.of(phone);
- Optional from null: Use
Optional.ofNullable
to create an optional that may hold anull
value. If the value isnull
, 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.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.