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 isnullaNullPointerExceptionis thrown immediately:Optional<Phone> optPhone = Optional.of(phone);
- Optional from null: Use
Optional.ofNullableto create an optional that may hold anullvalue. 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.