In the previous blog NullPointerException to Optional! , we discussed how Optional came to the world. In this blog, let’s look at its usage/misusage.
Optional has been a very controversial java-type Not just the incorrect use, often times you see a lot of unnecessary usage and silent nulls.
Optional<T>
indicates the presence or absence of a value of type T
.
The java.util.Optional
class is decorated with 3 static factory methods. Any of these can be used to create an instance of Optional.
public static <T> Optional<T> empty ()
public static <T> Optional<T> of (T value)
public static <T> Optional<T> ofNullable(T value)
Once you have the instance, the following are some (not all) significant methods available for use
public T get ()
public boolean isPresent ()
public void ifPresent (Consumer<? super T> consumer)public Optional<T> filter (Predicate<? super T> predicate) <U> Optional<U> map (Function<? super T, ? extends U> mapper)
<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)public T orElse(T other)
public T orElseGet(Supplier<? extends T> other)
public <X extends Throwable> T orElseThrow
Here are some of my recommendations for using Optional
1. Use Optional.empty()
Never return a null from a method with returns type as Optional<T>, instead, return Optional.empty()
Explanation: The whole reason for using Optional is, to have a wrapper over the absence or presence of a value. In some cases, the value can be absent (null) but the wrapper should always be present. Optional.empty()
returns a wrapper with an absent( null) value.
2. Do not overuse Optional
Before we talk about this point, let’s say, we should avoid returning nullable references in any new code. However, you will still face some (usually a lot) well-written library code or existing code that can successfully consume nullable references. In that case, it’s ok to return null, make sure the return type is not an Optional.
Now about Optional overuse.
As I said in earlier post, Optional is not a replacement for null, it’s just a tool with 16 bytes overhead, to help us write fluent code API with method chaining. I’ve seen the following type of code a lot more than you can imagine.
Car car = getCarFromSomewhere();
Optional<Car> carOptional = Optional.ofNullable(car);return carOptional.isPresent()
? carOptional.get().getMake();
: null ;
It is a working code but has a lot of unnecessary lines & Object allocation.
Avoid such code clutter by a simple null check
Car car = getCarFromSomewhere();
return (car != null)
? car.getMake()
: null ;
3. Be cautious using orElse() with multiple map()
If you really want to write a fluent pipeline for the above example, the below code will work well and test cases will also pass.
//nicest looking code for above example but read explanation
return Optional
.ofNullable(getCarFromSomewhere())
.map(Car::getMake)
.orElse(null);
But this code is doing more than what we assumed. For both null equalities (either car or make) it fallbacks to the same value from orElse()
.
The above code works well when you return null
, but if you need to return different values for null equality of car vs make, a clean pipeline is not an option
4. Use ofNullable() insteadof of()
of(value)
is a factory method to create the Optional wrapper of known non-null value. Use it only when you are defaulting to some known value in your method returns. In all other cases go with ofNullable
//Incorrect, It can through NullPointerException (NPE)
return Optional
.of(getCarFromSomewhere()) //use ofNullable() instead
.map(Car::getMake)
.orElse(null));
of()
method use Objects.requireNonNull(value)
for setting the value in Optional wrapper. which will throw NPE when value is null.
5. Don't call get() without verification
Never call get()
on an Optional instance without verifying the presence of value. No one makes this mistake anymore, if you see someone doing it, it's time for a serious talk.
Let's assume someone changed the return type forgetCarFromSomewhere()
and now it returns an Optional<Car>.
private Optional<Car> getCarFromSomewhere(){
…..
}
Let's look at 3 code blocks in some method which is consuming /calling getCarFromSomewhere()
//Wrong codeOptional<Car> carOptional = getCarFromSomewhere();
return carOptional.get(); // can throw NoSuchElementException
It can be written like below
//non error-prone but still cluttery codeOptional<Car> carOptional = getCarFromSomewhere();
return carOptional.isPresent()
? carOptional.get();
: null ;
The best way is, to change the return type of your consuming method so it can return an Optional instead of a nullable reference, but in the cases when you have to return nullable references, the below code is cleaner
return getCarFromSomewhere()
.orElse(null));
6. Use orElseGet() not orElse() for unconstructed fallback values
As we saw in the above code examples, it's better to use orElse()
instead of isPresent
& get
pair. But the caveat is, the code present in orElse()
is always evaluated even when Optional is not empty.
So if you always use orElse()
for Optional.empty scenarios, then
- In the worst cases, when you call a state-altering code in
orElse()
, States will be changed even for non-empty Optionals. (Bad Practice) - In best cases, it will be a performance hit as the code in
orElse
will still be executed
Nevertheless, An easy to remember rule is Don't use orElse() if you don’t have an already constructed object to return.
Explanation:
return getCarFromSomewhere()
.map(Car::getMake)
.orElse(getDefaultMake());private Make getDefaultMake(){
// increment defaultMakeRequested counter by one}
Even if getCarFromSomewhere()
always returns a non-empty Optional<Car>
and all of the Car always contain a non-null Make, we will still have defaultMakeRequested
increased by one, for every car.
The solution is
Use orElseGet()
for unconstructed fallback responses.
It accepts a Supplier
which gets executed only when the Optional chain faces a non-empty Optional. So our code should look like
return getCarFromSomewhere()
.map(Car::getMake)
.orElseGet(this::getDefaultMake);
7. Use dedicated Optional container for primitive type
Whenever you want Optional of primitive types int, long & double, prefer to use dedicated primitive Optional wrappers OptionalInt
, OptionalLong
& OptionalDouble.
Try to avoid using Optional<Integer>
, Optional<Long>
& Optional<Double>.
//Avoid
Optional<Integer> value = Optional.of(1);
Optional<Long> value = Optional.of(1L);
Optional<Double> value = Optional.of(1.1d);// Use
OptionalInt value = OptionalInt.of(1);
OptionalLong value = OptionalLong.of(1L);
OptionalDouble value = OptionalDouble.of(1.1d);
Explanation:
If you do not use dedicated wrappers (the first way in the above example), the runtime needs to perform autoboxing, which induces its own performance penalties.
Above are well-proven and widely accepted recommendations, But I do have some debatable recommendations, which I’ll be covering in my next blog.
Comments