What is Optional? 🤔
According to Oracle: "A container object which may or may not contain a non-null value."
Why use it? 👀
it was introduced in Java 8 to enable us to elegantly handle the Null pointer exception problem.
How?
Let's first start by learning how variables in Java are handled in memory and what NullPointerException means.
In java variables can be one of two types:
-
Primitives
- They are 8 types (int, char, etc...)
- They are saved in the execution stack of the current method.
- Fast info: each method call has its own execution stack.
public void someMethod(){
int x = 10;
}
-
Objects
- They are instances of classes (User, Car, Integer)
- They are saved in the Java Heap, however, references to them are saved in the execution stack of the current method as well.
- Those references are saved into variables.
public void someMethod(){
// the local variable car is a reference to the actual object value in the Heap
Car car = new Car();
}
Where does null fit in the picture? 🖼️
If an object isn't referencing any value in the Heap, then its value is null.
When we try to do some operations using that variable we hit the famous NullPointerException.
Car car;
car.getModel();
How does the Optional help?
It wraps the objects we pass to the calling method, and it provides methods to handle the presence or absence of the null value in a clean code functional style.
Code time 🤖
As an example, let's say we have a Room class, which has a Desk, and that Desk has Pen Holder
First, let's look at some code that doesn't use the Optional class
Room 🏠
@Getter @RequiredArgsConstructor @AllArgsConstructor
public class Room {
private Desk desk;
private final int number;
}
Desk 🗃
@Getter @RequiredArgsConstructor @AllArgsConstructor
public class Desk {
private PenHolder penHolder;
private final String model;
}
Pen Holder 🖊
@Getter @RequiredArgsConstructor
public class PenHolder {
private final int capacity;
}
A service that tells us what is the capacity of the Pen holder in a room, if exists. It has two methods, one that does a null check and another that doesn't
public class FetchingService {
// this method will throw a nullPointerException when
// either the Desk or PenHolder are null
public static int getCapacityOfPenHolder_NoNullCheck(Room room){
return room.
getDesk().
getPenHolder().
getCapacity();
}
// this method performs a null check on both Desk and PenHolder
public static int getCapacityOfPenHolder(Room room){
if (room.getDesk() == null){
return 0;
}
else if (room.getDesk().getPenHolder() == null){
return 0;
}
else return room.getDesk().getPenHolder().getCapacity();
}
}
Now let's apply Optional and see the results
Room 🏠
@AllArgsConstructor @RequiredArgsConstructor @Getter
public class Room {
private Desk desk;
private final int number;
public Optional<Desk> getDeskOpt(){
return Optional.ofNullable(desk);
}
}
Desk 🗃
@Getter @AllArgsConstructor @RequiredArgsConstructor
public class Desk {
private PenHolder penHolder;
private final String model;
public Optional<PenHolder> getPenHolderOpt(){
return Optional.ofNullable(penHolder);
}
}
Pen Holder 🖊
@RequiredArgsConstructor @Getter
public class PenHolder {
private final int capacity;
}
The service
public class FetchingService {
public static int getCapacityOfPenHolder(Room room){
return room.
getDeskOpt().
flatMap(Desk::getPenHolderOpt).
map(PenHolder::getCapacity).
orElse(0);
}
}
The optional code is much more concise, clear, and clean. However, the most important side is that we got rid of the NullPointerException monster.
One thing to be careful of is to not return null instead of the Optional. 🔥
public Optional<PenHolder> getPenHolderOpt(){
if (penHolder == null)
return null;
else return Optional.ofNullable(penHolder);
}
Where to use Optional?
There are a number of options for the place that we might use the Optional wrapper class, for example, wrap method parameters, local variables. However, the most suitable place is on method return types.
public class Desk {
// not good !!!!
private Optional<PenHolder> penHolder;
private final String model;
}
public class Desk {
private PenHolder penHolder;
private final String model;
// Yes !!!
public Optional<PenHolder> getPenHolderOpt(){
return Optional.ofNullable(penHolder);
}
}
Conclusion
The Optional class is a wrapper that wraps objects that might not exist in the Heap (in other words that could be null).
It was created to reduce the possibility of facing a NullPointerException
This class has methods that help us write clean code.
Top comments (2)
At best, I feel this is a bad example of
Optional
.Your code design allows for the fact that a desk may or may not be present, but always assumes that pens are within penholders on a desk, which begs the question "where is the pen holder when the desk is null? Is it on the floor? Window ledge? What happens when the desk is present, but someone (probably QA) leaves a pen on a chair?
Further I would argue that your original code, with it's NPE possibility, is infinitely more readable (the
FetchingService
shouldn't need to care if the desk exists or not, so shouldn't need tomap()
etc).Your NullPointerException could equally have been solved by making
Room.desk
immutable, much likeRoom.number
is... and in fact,Room.desk
is probably better as some type ofCollection
(since a Room can logically have 0 -> N desks). Then Streams API becomes more useful to you.I note from your bio here, that you work in Spring... returning
Optional
from aRepository
is an infinitely better example, in my opinion. We do not know until we look in the datastore, if the row(s) exist or not.NullPointerException
is a symptom of poor design thoughts,Optional
is for when the state is indeterminate even at runtime (and even then, returningnull
is a valid strategy).Thanks for your reply and arguments,
regarding the design choice, I chose this nested module of objects to demonstrate the null checks, I didn't really consider it to be a production example.
As for the readability issue in the FetchingService, I guess it's a matter of personal preference, for me the flatMap and map are better than the if-else statements.
The immutability of the Desk in the Room assumes that we can't add a Desk to the Room in the future. Moreover, the design choice of whether it's a single Desk or not isn't the issue we are dealing with in the post.
Yes, Optionals from the Repository is also a very good example for Optional usage.
I agree with your last statement about the cases we should use Optional (indeterminate state), which is why I left the Desk in the Room as Optional and not final.
Thanks again for your valuable feedback, I would love to hear more :)